Merge branch 'develop' into feat/miner-structured-logging

This commit is contained in:
pavitthrap
2022-01-06 12:03:33 -05:00
committed by GitHub
11 changed files with 1134 additions and 302 deletions

View File

@@ -54,6 +54,7 @@ jobs:
- tests::neon_integrations::filter_low_fee_tx_integration_test
- tests::neon_integrations::filter_long_runtime_tx_integration_test
- tests::neon_integrations::mining_transactions_is_fair
- tests::neon_integrations::use_latest_tip_integration_test
- tests::epoch_205::test_dynamic_db_method_costs
- tests::epoch_205::transition_empty_blocks
- tests::epoch_205::test_cost_limit_switch_version205

View File

@@ -73,8 +73,8 @@ paths:
in: query
schema:
type: string
description: The Stacks chain tip to query from
description: The Stacks chain tip to query from. If tip == latest, the query will be run from the latest
known tip (includes unconfirmed state).
/v2/map_entry/{contract_address}/{contract_name}/{map_name}:
post:
summary: Get specific data-map inside a contract
@@ -126,7 +126,8 @@ paths:
in: query
schema:
type: string
description: The Stacks chain tip to query from
description: The Stacks chain tip to query from. If tip == latest, the query will be run from the latest
known tip (includes unconfirmed state).
x-codegen-request-body-name: key
requestBody:
description: Hex string serialization of the lookup key (which should be a Clarity value)
@@ -174,7 +175,8 @@ paths:
in: query
schema:
type: string
description: The Stacks chain tip to query from
description: The Stacks chain tip to query from. If tip == latest, the query will be run from the latest
known tip (includes unconfirmed state).
required: false
/v2/contracts/call-read/{contract_address}/{contract_name}/{function_name}:
@@ -222,7 +224,8 @@ paths:
in: query
schema:
type: string
description: The Stacks chain tip to query from
description: The Stacks chain tip to query from. If tip == latest, the query will be run from the latest
known tip (includes unconfirmed state).
required: false
requestBody:
description: map of arguments and the simulated tx-sender where sender is either a Contract identifier or a normal Stacks address, and arguments is an array of hex serialized Clarity values.
@@ -265,7 +268,8 @@ paths:
in: query
schema:
type: string
description: The Stacks chain tip to query from
description: The Stacks chain tip to query from. If tip == latest, the query will be run from the latest
known tip (includes unconfirmed state).
responses:
200:
description: Success
@@ -416,6 +420,13 @@ paths:
$ref: ./api/core-node/get-pox.schema.json
example:
$ref: ./api/core-node/get-pox.example.json
parameters:
- name: tip
in: query
schema:
type: string
description: The Stacks chain tip to query from. If tip == latest, the query will be run from the latest
known tip (includes unconfirmed state).
/v2/traits/{contract_address}/{contract_name}/{trait_contract_address}/{trait_ contract_name}/{trait_name}:
get:
@@ -468,4 +479,7 @@ paths:
in: query
schema:
type: string
description: The Stacks chain tip to query from
description: |
The Stacks chain tip to query from.
If tip == "latest", the query will be run from the latest known tip (includes unconfirmed state).
If the tip is left unspecified, the stacks chain tip will be selected (only includes confirmed state).

View File

@@ -383,6 +383,35 @@ impl UnconfirmedState {
0
}
}
/// Try returning the unconfirmed chain tip. Only return the tip if the underlying MARF trie
/// exists, otherwise return None.
pub fn get_unconfirmed_state_if_exists(&mut self) -> Result<Option<StacksBlockId>, String> {
if self.is_readable() {
let trie_exists = match self
.clarity_inst
.trie_exists_for_block(&self.unconfirmed_chain_tip)
{
Ok(res) => res,
Err(e) => {
let err_str = format!(
"Failed to load Stacks chain tip; error checking underlying trie: {}",
e
);
warn!("{}", err_str);
return Err(err_str);
}
};
if trie_exists {
Ok(Some(self.unconfirmed_chain_tip))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
}
impl StacksChainState {

View File

@@ -68,6 +68,7 @@ use crate::types::chainstate::StacksBlockId;
use crate::types::chainstate::StacksMicroblockHeader;
use crate::types::proof::TrieHash;
use crate::util::boot::{boot_code_acc, boot_code_addr, boot_code_id, boot_code_tx_auth};
use crate::util::db::Error as db_error;
use crate::util::secp256k1::MessageSignature;
use types::chainstate::BurnchainHeaderHash;
@@ -517,6 +518,11 @@ impl ClarityInstance {
})
}
pub fn trie_exists_for_block(&mut self, bhh: &StacksBlockId) -> Result<bool, db_error> {
let mut datastore = self.datastore.begin_read_only(None);
datastore.trie_exists_for_block(bhh)
}
/// Evaluate program read-only at `at_block`. This will be evaluated in the Stacks epoch that
/// was active *during* the evaluation of `at_block`
pub fn eval_read_only(

View File

@@ -16,6 +16,7 @@ use vm::types::QualifiedContractIdentifier;
use crate::types::chainstate::{BlockHeaderHash, StacksBlockHeader};
use crate::types::chainstate::{MARFValue, StacksBlockId};
use crate::types::proof::{ClarityMarfTrieId, TrieHash, TrieMerkleProof};
use crate::util::db::Error as db_error;
/// The MarfedKV struct is used to wrap a MARF data structure and side-storage
/// for use as a K/V store for ClarityDB or the AnalysisDB.
@@ -267,6 +268,13 @@ impl<'a> ReadOnlyMarfStore<'a> {
pub fn as_analysis_db<'b>(&'b mut self) -> AnalysisDatabase<'b> {
AnalysisDatabase::new(self)
}
pub fn trie_exists_for_block(&mut self, bhh: &StacksBlockId) -> Result<bool, db_error> {
self.marf.with_conn(|conn| match conn.has_block(bhh) {
Ok(res) => Ok(res),
Err(e) => Err(db_error::IndexError(e)),
})
}
}
impl<'a> ClarityBackingStore for ReadOnlyMarfStore<'a> {

View File

@@ -41,7 +41,6 @@ use chainstate::burn::ConsensusHash;
use chainstate::stacks::{StacksBlock, StacksMicroblock, StacksPublicKey, StacksTransaction};
use deps::httparse;
use net::atlas::Attachment;
use net::CallReadOnlyRequestBody;
use net::ClientError;
use net::Error as net_error;
use net::Error::ClarityError;
@@ -68,6 +67,7 @@ use net::HTTP_PREAMBLE_MAX_NUM_HEADERS;
use net::HTTP_REQUEST_ID_RESERVED;
use net::MAX_HEADERS;
use net::MAX_MICROBLOCKS_UNCONFIRMED;
use net::{CallReadOnlyRequestBody, TipRequest};
use net::{GetAttachmentResponse, GetAttachmentsInvResponse, PostTransactionRequestBody};
use util::hash::hex_bytes;
use util::hash::to_hex;
@@ -1681,9 +1681,8 @@ impl HttpRequestType {
))
}
/// check whether the given option query string
/// sets proof=0 (setting proof to false).
/// Defaults to _true_
/// Check whether the given option query string sets proof=0 (setting proof to false).
/// Defaults to true.
fn get_proof_query(query: Option<&str>) -> bool {
let no_proof = if let Some(query_string) = query {
form_urlencoded::parse(query_string.as_bytes())
@@ -1699,7 +1698,7 @@ impl HttpRequestType {
/// get the chain tip optional query argument (`tip`)
/// Take the first value we can parse.
fn get_chain_tip_query(query: Option<&str>) -> Option<StacksBlockId> {
fn get_chain_tip_query(query: Option<&str>) -> TipRequest {
match query {
Some(query_string) => {
for (key, value) in form_urlencoded::parse(query_string.as_bytes()) {
@@ -1707,14 +1706,17 @@ impl HttpRequestType {
continue;
}
if value == "latest" {
return TipRequest::UseLatestUnconfirmedTip;
}
if let Ok(tip) = StacksBlockId::from_hex(&value) {
return Some(tip);
return TipRequest::SpecificTip(tip);
}
}
return None;
return TipRequest::UseLatestAnchoredTip;
}
None => {
return None;
return TipRequest::UseLatestAnchoredTip;
}
}
}
@@ -2558,7 +2560,7 @@ impl HttpRequestType {
pub fn metadata(&self) -> &HttpRequestMetadata {
match *self {
HttpRequestType::GetInfo(ref md) => md,
HttpRequestType::GetPoxInfo(ref md, _) => md,
HttpRequestType::GetPoxInfo(ref md, ..) => md,
HttpRequestType::GetNeighbors(ref md) => md,
HttpRequestType::GetHeaders(ref md, ..) => md,
HttpRequestType::GetBlock(ref md, _) => md,
@@ -2588,7 +2590,7 @@ impl HttpRequestType {
pub fn metadata_mut(&mut self) -> &mut HttpRequestMetadata {
match *self {
HttpRequestType::GetInfo(ref mut md) => md,
HttpRequestType::GetPoxInfo(ref mut md, _) => md,
HttpRequestType::GetPoxInfo(ref mut md, ..) => md,
HttpRequestType::GetNeighbors(ref mut md) => md,
HttpRequestType::GetHeaders(ref mut md, ..) => md,
HttpRequestType::GetBlock(ref mut md, _) => md,
@@ -2615,36 +2617,36 @@ impl HttpRequestType {
}
}
fn make_query_string(tip_opt: Option<&StacksBlockId>, with_proof: bool) -> String {
if let Some(tip) = tip_opt {
format!(
"?tip={}{}",
tip,
if with_proof { "&proof=1" } else { "&proof=0" }
)
} else if !with_proof {
format!("?proof=0")
} else {
"".to_string()
fn make_query_string(tip_req: &TipRequest, with_proof: bool) -> String {
match tip_req {
TipRequest::UseLatestUnconfirmedTip => {
format!("?tip=latest{}", if with_proof { "" } else { "&proof=0" })
}
TipRequest::SpecificTip(tip) => {
format!("?tip={}{}", tip, if with_proof { "" } else { "&proof=0" })
}
TipRequest::UseLatestAnchoredTip => {
if !with_proof {
format!("?proof=0")
} else {
"".to_string()
}
}
}
}
pub fn request_path(&self) -> String {
match self {
HttpRequestType::GetInfo(_md) => "/v2/info".to_string(),
HttpRequestType::GetPoxInfo(_md, tip_opt) => format!(
HttpRequestType::GetPoxInfo(_md, tip_req) => format!(
"/v2/pox{}",
HttpRequestType::make_query_string(tip_opt.as_ref(), true)
HttpRequestType::make_query_string(tip_req, true)
),
HttpRequestType::GetNeighbors(_md) => "/v2/neighbors".to_string(),
HttpRequestType::GetHeaders(_md, quantity, tip_opt) => format!(
HttpRequestType::GetHeaders(_md, quantity, tip_req) => format!(
"/v2/headers/{}{}",
quantity,
if let Some(ref tip) = tip_opt {
format!("?tip={}", tip)
} else {
"".to_string()
}
HttpRequestType::make_query_string(tip_req, true)
),
HttpRequestType::GetBlock(_md, block_hash) => {
format!("/v2/blocks/{}", block_hash.to_hex())
@@ -2665,28 +2667,30 @@ impl HttpRequestType {
}
HttpRequestType::PostTransaction(_md, ..) => "/v2/transactions".to_string(),
HttpRequestType::PostBlock(_md, ch, ..) => format!("/v2/blocks/upload/{}", &ch),
HttpRequestType::PostMicroblock(_md, _, tip_opt) => format!(
HttpRequestType::PostMicroblock(_md, _, tip_req) => format!(
"/v2/microblocks{}",
HttpRequestType::make_query_string(tip_opt.as_ref(), true)
),
HttpRequestType::GetAccount(_md, principal, tip_opt, with_proof) => format!(
"/v2/accounts/{}{}",
&principal.to_string(),
HttpRequestType::make_query_string(tip_opt.as_ref(), *with_proof)
HttpRequestType::make_query_string(tip_req, true)
),
HttpRequestType::GetAccount(_md, principal, tip_req, with_proof) => {
format!(
"/v2/accounts/{}{}",
&principal.to_string(),
HttpRequestType::make_query_string(tip_req, *with_proof,)
)
}
HttpRequestType::GetDataVar(
_md,
contract_addr,
contract_name,
var_name,
tip_opt,
tip_req,
with_proof,
) => format!(
"/v2/data_var/{}/{}/{}{}",
&contract_addr.to_string(),
contract_name.as_str(),
var_name.as_str(),
HttpRequestType::make_query_string(tip_opt.as_ref(), *with_proof)
HttpRequestType::make_query_string(tip_req, *with_proof)
),
HttpRequestType::GetMapEntry(
_md,
@@ -2694,40 +2698,40 @@ impl HttpRequestType {
contract_name,
map_name,
_key,
tip_opt,
tip_req,
with_proof,
) => format!(
"/v2/map_entry/{}/{}/{}{}",
&contract_addr.to_string(),
contract_name.as_str(),
map_name.as_str(),
HttpRequestType::make_query_string(tip_opt.as_ref(), *with_proof)
HttpRequestType::make_query_string(tip_req, *with_proof)
),
HttpRequestType::GetTransferCost(_md) => "/v2/fees/transfer".into(),
HttpRequestType::GetContractABI(_, contract_addr, contract_name, tip_opt) => format!(
HttpRequestType::GetContractABI(_, contract_addr, contract_name, tip_req) => format!(
"/v2/contracts/interface/{}/{}{}",
contract_addr,
contract_name.as_str(),
HttpRequestType::make_query_string(tip_opt.as_ref(), true)
HttpRequestType::make_query_string(tip_req, true,)
),
HttpRequestType::GetContractSrc(
_,
contract_addr,
contract_name,
tip_opt,
tip_req,
with_proof,
) => format!(
"/v2/contracts/source/{}/{}{}",
contract_addr,
contract_name.as_str(),
HttpRequestType::make_query_string(tip_opt.as_ref(), *with_proof)
HttpRequestType::make_query_string(tip_req, *with_proof)
),
HttpRequestType::GetIsTraitImplemented(
_,
contract_addr,
contract_name,
trait_id,
tip_opt,
tip_req,
) => format!(
"/v2/traits/{}/{}/{}/{}/{}{}",
contract_addr,
@@ -2735,7 +2739,7 @@ impl HttpRequestType {
trait_id.name.to_string(),
StacksAddress::from(trait_id.clone().contract_identifier.issuer),
trait_id.contract_identifier.name.as_str(),
HttpRequestType::make_query_string(tip_opt.as_ref(), true)
HttpRequestType::make_query_string(tip_req, true)
),
HttpRequestType::CallReadOnlyFunction(
_,
@@ -2744,13 +2748,13 @@ impl HttpRequestType {
_,
func_name,
_,
tip_opt,
tip_req,
) => format!(
"/v2/contracts/call-read/{}/{}/{}{}",
contract_addr,
contract_name.as_str(),
func_name.as_str(),
HttpRequestType::make_query_string(tip_opt.as_ref(), true)
HttpRequestType::make_query_string(tip_req, true)
),
HttpRequestType::OptionsPreflight(_md, path) => path.to_string(),
HttpRequestType::GetAttachmentsInv(_md, index_block_hash, pages_indexes) => {
@@ -6575,46 +6579,58 @@ mod test {
#[test]
fn test_http_parse_proof_tip_query() {
let query_txt = "tip=7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392";
assert_eq!(
HttpRequestType::get_chain_tip_query(Some(query_txt)).unwrap(),
StacksBlockId::from_hex(
"7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392"
)
.unwrap()
);
let tip_req = HttpRequestType::get_chain_tip_query(Some(query_txt));
match tip_req {
TipRequest::SpecificTip(tip) => assert_eq!(
tip,
StacksBlockId::from_hex(
"7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392"
)
.unwrap()
),
_ => panic!(),
}
// first parseable tip is taken
let query_txt_dup = "tip=7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392&tip=03e26bd68a8722f8b3861e2058edcafde094ad059e152754986c3573306698f1";
assert_eq!(
HttpRequestType::get_chain_tip_query(Some(query_txt_dup)).unwrap(),
StacksBlockId::from_hex(
"7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392"
)
.unwrap()
);
let tip_req = HttpRequestType::get_chain_tip_query(Some(query_txt));
match tip_req {
TipRequest::SpecificTip(tip) => assert_eq!(
tip,
StacksBlockId::from_hex(
"7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392"
)
.unwrap()
),
_ => panic!(),
}
// first parseable tip is taken
let query_txt_dup = "tip=bad&tip=7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392&tip=03e26bd68a8722f8b3861e2058edcafde094ad059e152754986c3573306698f1";
assert_eq!(
HttpRequestType::get_chain_tip_query(Some(query_txt_dup)).unwrap(),
StacksBlockId::from_hex(
"7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392"
)
.unwrap()
);
let tip_req = HttpRequestType::get_chain_tip_query(Some(query_txt_dup));
match tip_req {
TipRequest::SpecificTip(tip) => assert_eq!(
tip,
StacksBlockId::from_hex(
"7070f213d719143d6045e08fd80f85014a161f8bbd3a42d1251576740826a392"
)
.unwrap()
),
_ => panic!(),
}
// tip can be skipped
let query_txt_bad = "tip=bad";
assert_eq!(
HttpRequestType::get_chain_tip_query(Some(query_txt_bad)),
None
TipRequest::UseLatestAnchoredTip
);
// tip can be skipped
let query_txt_none = "tip=bad";
assert_eq!(
HttpRequestType::get_chain_tip_query(Some(query_txt_none)),
None
TipRequest::UseLatestAnchoredTip
);
}

View File

@@ -1002,8 +1002,8 @@ pub struct RPCPeerInfoData {
pub stacks_tip: BlockHeaderHash,
pub stacks_tip_consensus_hash: ConsensusHash,
pub genesis_chainstate_hash: Sha256Sum,
pub unanchored_tip: StacksBlockId,
pub unanchored_seq: u16,
pub unanchored_tip: Option<StacksBlockId>,
pub unanchored_seq: Option<u16>,
pub exit_at_block_height: Option<u64>,
}
@@ -1341,13 +1341,20 @@ pub struct RPCNeighborsInfo {
pub outbound: Vec<RPCNeighbor>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TipRequest {
UseLatestAnchoredTip,
UseLatestUnconfirmedTip,
SpecificTip(StacksBlockId),
}
/// All HTTP request paths we support, and the arguments they carry in their paths
#[derive(Debug, Clone, PartialEq)]
pub enum HttpRequestType {
GetInfo(HttpRequestMetadata),
GetPoxInfo(HttpRequestMetadata, Option<StacksBlockId>),
GetPoxInfo(HttpRequestMetadata, TipRequest),
GetNeighbors(HttpRequestMetadata),
GetHeaders(HttpRequestMetadata, u64, Option<StacksBlockId>),
GetHeaders(HttpRequestMetadata, u64, TipRequest),
GetBlock(HttpRequestMetadata, StacksBlockId),
GetMicroblocksIndexed(HttpRequestMetadata, StacksBlockId),
GetMicroblocksConfirmed(HttpRequestMetadata, StacksBlockId),
@@ -1355,19 +1362,14 @@ pub enum HttpRequestType {
GetTransactionUnconfirmed(HttpRequestMetadata, Txid),
PostTransaction(HttpRequestMetadata, StacksTransaction, Option<Attachment>),
PostBlock(HttpRequestMetadata, ConsensusHash, StacksBlock),
PostMicroblock(HttpRequestMetadata, StacksMicroblock, Option<StacksBlockId>),
GetAccount(
HttpRequestMetadata,
PrincipalData,
Option<StacksBlockId>,
bool,
),
PostMicroblock(HttpRequestMetadata, StacksMicroblock, TipRequest),
GetAccount(HttpRequestMetadata, PrincipalData, TipRequest, bool),
GetDataVar(
HttpRequestMetadata,
StacksAddress,
ContractName,
ClarityName,
Option<StacksBlockId>,
TipRequest,
bool,
),
GetMapEntry(
@@ -1376,7 +1378,7 @@ pub enum HttpRequestType {
ContractName,
ClarityName,
Value,
Option<StacksBlockId>,
TipRequest,
bool,
),
FeeRateEstimate(HttpRequestMetadata, TransactionPayload, u64),
@@ -1387,22 +1389,17 @@ pub enum HttpRequestType {
PrincipalData,
ClarityName,
Vec<Value>,
Option<StacksBlockId>,
TipRequest,
),
GetTransferCost(HttpRequestMetadata),
GetContractSrc(
HttpRequestMetadata,
StacksAddress,
ContractName,
Option<StacksBlockId>,
TipRequest,
bool,
),
GetContractABI(
HttpRequestMetadata,
StacksAddress,
ContractName,
Option<StacksBlockId>,
),
GetContractABI(HttpRequestMetadata, StacksAddress, ContractName, TipRequest),
OptionsPreflight(HttpRequestMetadata, String),
GetAttachment(HttpRequestMetadata, Hash160),
GetAttachmentsInv(HttpRequestMetadata, StacksBlockId, HashSet<u32>),
@@ -1411,7 +1408,7 @@ pub enum HttpRequestType {
StacksAddress,
ContractName,
TraitIdentifier,
Option<StacksBlockId>,
TipRequest,
),
/// catch-all for any errors we should surface from parsing
ClientError(HttpRequestMetadata, ClientError),

View File

@@ -2448,7 +2448,8 @@ mod test {
let mut request = HttpRequestMetadata::new("127.0.0.1".to_string(), http_port);
request.keep_alive = false;
let tip = StacksBlockHeader::make_index_block_hash(consensus_hash, block_hash);
let post_microblock = HttpRequestType::PostMicroblock(request, mblock.clone(), Some(tip));
let post_microblock =
HttpRequestType::PostMicroblock(request, mblock.clone(), TipRequest::SpecificTip(tip));
let response = http_rpc(http_port, post_microblock).unwrap();
if let HttpResponseType::MicroblockHash(..) = response {
return true;

File diff suppressed because it is too large Load Diff

View File

@@ -849,6 +849,18 @@ fn integration_test_get_info() {
eprintln!("Test: GET {}", path);
assert!(!res.is_implemented);
// test query parameters for v2/trait endpoint
// evaluate check for explicit compliance against the chain tip of the first block (contract DNE at that block)
let path = format!("{}/v2/traits/{}/{}/{}/{}/{}?tip=753d84de5c475a85abd0eeb3ac87da03ff0f794507b60a3f66356425bc1dedaf", &http_origin, &contract_addr, "impl-trait-contract", &contract_addr, "get-info", "trait-1");
let res = client.get(&path).send().unwrap();
eprintln!("Test: GET {}", path);
assert_eq!(res.text().unwrap(), "No contract analysis found or trait definition not found");
// evaluate check for explicit compliance where tip is the chain tip of the first block (contract DNE at that block), but tip is "latest"
let path = format!("{}/v2/traits/{}/{}/{}/{}/{}?tip=latest", &http_origin, &contract_addr, "impl-trait-contract", &contract_addr, "get-info", "trait-1");
let res = client.get(&path).send().unwrap().json::<GetIsTraitImplementedResponse>().unwrap();
eprintln!("Test: GET {}", path);
assert!(res.is_implemented);
// perform some tests of the fee rate interface
let path = format!("{}/v2/fees/transaction", &http_origin);

View File

@@ -21,7 +21,7 @@ use stacks::core;
use stacks::core::CHAIN_ID_TESTNET;
use stacks::net::atlas::{AtlasConfig, AtlasDB, MAX_ATTACHMENT_INV_PAGES_PER_REQUEST};
use stacks::net::{
AccountEntryResponse, GetAttachmentResponse, GetAttachmentsInvResponse,
AccountEntryResponse, ContractSrcResponse, GetAttachmentResponse, GetAttachmentsInvResponse,
PostTransactionRequestBody, RPCPeerInfoData,
};
use stacks::types::chainstate::{
@@ -725,6 +725,33 @@ fn get_chain_tip_height(http_origin: &str) -> u64 {
res.stacks_tip_height
}
fn get_contract_src(
http_origin: &str,
contract_addr: StacksAddress,
contract_name: String,
use_latest_tip: bool,
) -> Result<String, String> {
let client = reqwest::blocking::Client::new();
let query_string = if use_latest_tip {
"?tip=latest".to_string()
} else {
"".to_string()
};
let path = format!(
"{}/v2/contracts/source/{}/{}{}",
http_origin, contract_addr, contract_name, query_string
);
let res = client.get(&path).send().unwrap();
if res.status().is_success() {
let contract_src_res = res.json::<ContractSrcResponse>().unwrap();
Ok(contract_src_res.source)
} else {
let err_str = res.text().unwrap();
Err(err_str)
}
}
#[test]
#[ignore]
fn liquid_ustx_integration() {
@@ -1691,16 +1718,17 @@ fn microblock_integration_test() {
let tip_info = loop {
let tip_info = get_chain_info(&conf);
eprintln!("{:#?}", tip_info);
if tip_info.unanchored_tip == StacksBlockId([0; 32]) {
iter_count += 1;
assert!(
iter_count < 10,
"Hit retry count while waiting for net module to process pushed microblock"
);
sleep_ms(5_000);
continue;
} else {
break tip_info;
match tip_info.unanchored_tip {
None => {
iter_count += 1;
assert!(
iter_count < 10,
"Hit retry count while waiting for net module to process pushed microblock"
);
sleep_ms(5_000);
continue;
}
Some(_tip) => break tip_info,
}
};
@@ -1857,7 +1885,9 @@ fn microblock_integration_test() {
// we can query unconfirmed state from the microblock we announced
let path = format!(
"{}/v2/accounts/{}?proof=0&tip={}",
&http_origin, &spender_addr, &tip_info.unanchored_tip
&http_origin,
&spender_addr,
&tip_info.unanchored_tip.unwrap()
);
eprintln!("{:?}", &path);
@@ -1934,7 +1964,9 @@ fn microblock_integration_test() {
// we can query _new_ unconfirmed state from the microblock we announced
let path = format!(
"{}/v2/accounts/{}?proof=0&tip={}",
&http_origin, &spender_addr, &tip_info.unanchored_tip
&http_origin,
&spender_addr,
&tip_info.unanchored_tip.unwrap()
);
let res_text = client.get(&path).send().unwrap().text().unwrap();
@@ -6069,3 +6101,244 @@ fn atlas_stress_integration_test() {
test_observer::clear();
}
#[test]
#[ignore]
fn use_latest_tip_integration_test() {
// The purpose of this test is to check if setting the query parameter `tip` to `latest` is working
// as expected. Multiple endpoints accept this parameter, and in this test, we are using the
// GetContractSrc method to test it.
//
// The following scenarios are tested here:
// - The caller does not specify the tip paramater, and the canonical chain tip is used regardless of the
// state of the unconfirmed microblock stream.
// - The caller passes tip=latest with an existing unconfirmed microblock stream, and
// Clarity state from the unconfirmed microblock stream is successfully loaded.
// - The caller passes tip=latest with an empty unconfirmed microblock stream, and
// Clarity state from the canonical chain tip is successfully loaded (i.e. you don't
// get a 404 even though the unconfirmed chain tip points to a nonexistent MARF trie).
//
// Note: In this test, we are manually creating a microblock as well as reloading the unconfirmed
// state of the chainstate, instead of relying on `next_block_and_wait` to generate
// microblocks. We do this because the unconfirmed state is not automatically being initialized
// on the node, so attempting to validate any transactions against the expected unconfirmed
// state fails.
if env::var("BITCOIND_TEST") != Ok("1".into()) {
return;
}
let spender_sk = StacksPrivateKey::from_hex(SK_1).unwrap();
let spender_stacks_addr = to_addr(&spender_sk);
let spender_addr: PrincipalData = spender_stacks_addr.into();
let (mut conf, _) = neon_integration_test_conf();
conf.initial_balances.push(InitialBalance {
address: spender_addr.clone(),
amount: 100300,
});
conf.node.mine_microblocks = true;
conf.node.wait_time_for_microblocks = 10_000;
conf.node.microblock_frequency = 1_000;
test_observer::spawn();
conf.events_observers.push(EventObserverConfig {
endpoint: format!("localhost:{}", test_observer::EVENT_OBSERVER_PORT),
events_keys: vec![EventKeyType::AnyEvent],
});
let mut btcd_controller = BitcoinCoreController::new(conf.clone());
btcd_controller
.start_bitcoind()
.map_err(|_e| ())
.expect("Failed starting bitcoind");
let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None);
let http_origin = format!("http://{}", &conf.node.rpc_bind);
btc_regtest_controller.bootstrap_chain(201);
eprintln!("Chain bootstrapped...");
let mut run_loop = neon::RunLoop::new(conf.clone());
let blocks_processed = run_loop.get_blocks_processed_arc();
thread::spawn(move || run_loop.start(None, 0));
// Give the run loop some time to start up!
wait_for_runloop(&blocks_processed);
// First block wakes up the run loop.
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
// Second block will hold our VRF registration.
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
// Third block will be the first mined Stacks block.
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
// Let's query our first spender.
let account = get_account(&http_origin, &spender_addr);
assert_eq!(account.balance, 100300);
assert_eq!(account.nonce, 0);
// this call wakes up our node
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
// Open chainstate.
// TODO (hack) instantiate the sortdb in the burnchain
let _ = btc_regtest_controller.sortdb_mut();
let (consensus_hash, stacks_block) = get_tip_anchored_block(&conf);
let tip_hash =
StacksBlockHeader::make_index_block_hash(&consensus_hash, &stacks_block.block_hash());
let (mut chainstate, _) =
StacksChainState::open(false, CHAIN_ID_TESTNET, &conf.get_chainstate_path_str()).unwrap();
// Initialize the unconfirmed state.
chainstate
.reload_unconfirmed_state(&btc_regtest_controller.sortdb_ref().index_conn(), tip_hash)
.unwrap();
// Make microblock with two transactions.
let recipient = StacksAddress::from_string(ADDR_4).unwrap();
let transfer_tx =
make_stacks_transfer_mblock_only(&spender_sk, 0, 1000, &recipient.into(), 1000);
let caller_src = "
(define-public (execute)
(ok stx-liquid-supply))
";
let publish_tx =
make_contract_publish_microblock_only(&spender_sk, 1, 1000, "caller", caller_src);
let tx_1 = StacksTransaction::consensus_deserialize(&mut &transfer_tx[..]).unwrap();
let tx_2 = StacksTransaction::consensus_deserialize(&mut &publish_tx[..]).unwrap();
let vec_tx = vec![tx_1, tx_2];
let privk =
find_microblock_privkey(&conf, &stacks_block.header.microblock_pubkey_hash, 1024).unwrap();
let mblock = make_microblock(
&privk,
&mut chainstate,
&btc_regtest_controller.sortdb_ref().index_conn(),
consensus_hash,
stacks_block.clone(),
vec_tx,
);
let mut mblock_bytes = vec![];
mblock.consensus_serialize(&mut mblock_bytes).unwrap();
let client = reqwest::blocking::Client::new();
// Post the microblock
let path = format!("{}/v2/microblocks", &http_origin);
let res: String = client
.post(&path)
.header("Content-Type", "application/octet-stream")
.body(mblock_bytes.clone())
.send()
.unwrap()
.json()
.unwrap();
assert_eq!(res, format!("{}", &mblock.block_hash()));
// Wait for the microblock to be accepted
sleep_ms(5_000);
let path = format!("{}/v2/info", &http_origin);
let mut iter_count = 0;
loop {
let tip_info = client
.get(&path)
.send()
.unwrap()
.json::<RPCPeerInfoData>()
.unwrap();
eprintln!("{:#?}", tip_info);
if tip_info.unanchored_tip == Some(StacksBlockId([0; 32])) {
iter_count += 1;
assert!(
iter_count < 10,
"Hit retry count while waiting for net module to process pushed microblock"
);
sleep_ms(5_000);
continue;
} else {
break;
}
}
// Wait at least two p2p refreshes so it can produce the microblock.
for i in 0..30 {
info!(
"wait {} more seconds for microblock miner to find our transaction...",
30 - i
);
sleep_ms(1000);
}
// Check event observer for new microblock event (expect 1).
let microblock_events = test_observer::get_microblocks();
assert_eq!(microblock_events.len(), 1);
// Don't set the tip parameter, and ask for the source of the contract we just defined in a microblock.
// This should fail because the anchored tip would be unaware of this contract.
let err_opt = get_contract_src(
&http_origin,
spender_stacks_addr,
"caller".to_string(),
false,
);
match err_opt {
Ok(_) => {
panic!(
"Asking for the contract source off the anchored tip for a contract published \
only in unconfirmed state should error."
);
}
// Expect to get "NoSuchContract" because the function we are attempting to call is in a
// contract that only exists on unconfirmed state (and we did not set tip).
Err(err_str) => {
assert!(err_str.contains("No contract source data found"));
}
}
// Set tip=latest, and ask for the source of the contract defined in the microblock.
// This should succeeed.
assert!(get_contract_src(
&http_origin,
spender_stacks_addr,
"caller".to_string(),
true,
)
.is_ok());
// Mine an anchored block because now we want to have no unconfirmed state.
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
// Check that the underlying trie for the unconfirmed state does not exist.
assert!(chainstate.unconfirmed_state.is_some());
let unconfirmed_state = chainstate.unconfirmed_state.as_mut().unwrap();
let trie_exists = match unconfirmed_state
.clarity_inst
.trie_exists_for_block(&unconfirmed_state.unconfirmed_chain_tip)
{
Ok(res) => res,
Err(e) => {
panic!("error when determining whether or not trie exists: {:?}", e);
}
};
assert!(!trie_exists);
// Set tip=latest, and ask for the source of the contract defined in the previous epoch.
// The underlying MARF trie for the unconfirmed tip does not exist, so the transaction will be
// validated against the confirmed chain tip instead of the unconfirmed tip. This should be valid.
assert!(get_contract_src(
&http_origin,
spender_stacks_addr,
"caller".to_string(),
true,
)
.is_ok());
}