mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-23 19:31:00 +08:00
Merge branch 'develop' into feat/miner-structured-logging
This commit is contained in:
1
.github/workflows/bitcoin-tests.yml
vendored
1
.github/workflows/bitcoin-tests.yml
vendored
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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> {
|
||||
|
||||
158
src/net/http.rs
158
src/net/http.rs
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
851
src/net/rpc.rs
851
src/net/rpc.rs
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user