test: add integration test verifying block height values

This commit is contained in:
Brice Dobry
2024-05-07 21:43:19 -04:00
parent 2dc5bc3c41
commit fd90705f3c
3 changed files with 470 additions and 13 deletions

View File

@@ -21,7 +21,7 @@ use clarity::vm::costs::ExecutionCost;
use clarity::vm::database::BurnStateDB;
use clarity::vm::events::STXEventType;
use clarity::vm::types::PrincipalData;
use clarity::vm::{ClarityName, ContractName, Value};
use clarity::vm::{ClarityName, ClarityVersion, ContractName, Value};
use lazy_static::lazy_static;
use rand::RngCore;
use stacks::chainstate::burn::ConsensusHash;
@@ -223,19 +223,54 @@ pub fn serialize_sign_tx_anchor_mode_version(
buf
}
pub fn make_contract_publish_versioned(
sender: &StacksPrivateKey,
nonce: u64,
tx_fee: u64,
contract_name: &str,
contract_content: &str,
version: Option<ClarityVersion>,
) -> Vec<u8> {
let name = ContractName::from(contract_name);
let code_body = StacksString::from_string(&contract_content.to_string()).unwrap();
let payload =
TransactionPayload::SmartContract(TransactionSmartContract { name, code_body }, version);
serialize_sign_standard_single_sig_tx(payload, sender, nonce, tx_fee)
}
pub fn make_contract_publish(
sender: &StacksPrivateKey,
nonce: u64,
tx_fee: u64,
contract_name: &str,
contract_content: &str,
) -> Vec<u8> {
make_contract_publish_versioned(sender, nonce, tx_fee, contract_name, contract_content, None)
}
pub fn make_contract_publish_microblock_only_versioned(
sender: &StacksPrivateKey,
nonce: u64,
tx_fee: u64,
contract_name: &str,
contract_content: &str,
version: Option<ClarityVersion>,
) -> Vec<u8> {
let name = ContractName::from(contract_name);
let code_body = StacksString::from_string(&contract_content.to_string()).unwrap();
let payload = TransactionSmartContract { name, code_body };
let payload =
TransactionPayload::SmartContract(TransactionSmartContract { name, code_body }, version);
serialize_sign_standard_single_sig_tx(payload.into(), sender, nonce, tx_fee)
serialize_sign_standard_single_sig_tx_anchor_mode(
payload,
sender,
nonce,
tx_fee,
TransactionAnchorMode::OffChainOnly,
)
}
pub fn make_contract_publish_microblock_only(
@@ -245,17 +280,13 @@ pub fn make_contract_publish_microblock_only(
contract_name: &str,
contract_content: &str,
) -> Vec<u8> {
let name = ContractName::from(contract_name);
let code_body = StacksString::from_string(&contract_content.to_string()).unwrap();
let payload = TransactionSmartContract { name, code_body };
serialize_sign_standard_single_sig_tx_anchor_mode(
payload.into(),
make_contract_publish_microblock_only_versioned(
sender,
nonce,
tx_fee,
TransactionAnchorMode::OffChainOnly,
contract_name,
contract_content,
None,
)
}

View File

@@ -24,6 +24,7 @@ use std::{env, thread};
use clarity::vm::ast::ASTRules;
use clarity::vm::costs::ExecutionCost;
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
use clarity::vm::ClarityVersion;
use http_types::headers::AUTHORIZATION;
use lazy_static::lazy_static;
use libsigner::{BlockProposalSigners, SignerMessage, SignerSession, StackerDBSession};
@@ -82,10 +83,13 @@ use crate::neon::{Counters, RunLoopCounter};
use crate::operations::BurnchainOpSigner;
use crate::run_loop::boot_nakamoto;
use crate::tests::neon_integrations::{
get_account, get_chain_info_result, get_pox_info, next_block_and_wait,
call_read_only, get_account, get_chain_info_result, get_pox_info, next_block_and_wait,
run_until_burnchain_height, submit_tx, test_observer, wait_for_runloop,
};
use crate::tests::{get_chain_info, make_stacks_transfer, to_addr};
use crate::tests::{
get_chain_info, make_contract_publish, make_contract_publish_versioned, make_stacks_transfer,
to_addr,
};
use crate::{tests, BitcoinRegtestController, BurnchainController, Config, ConfigFile, Keychain};
pub static POX_4_DEFAULT_STACKER_BALANCE: u64 = 100_000_000_000_000;
@@ -3454,3 +3458,380 @@ fn forked_tenure_is_ignored() {
run_loop_thread.join().unwrap();
}
#[test]
#[ignore]
/// This test spins up a nakamoto-neon node.
/// It starts in Epoch 2.0, mines with `neon_node` to Epoch 3.0, and then switches
/// to Nakamoto operation (activating pox-4 by submitting a stack-stx tx). The BootLoop
/// struct handles the epoch-2/3 tear-down and spin-up.
/// This test makes three assertions:
/// * 5 tenures are mined after 3.0 starts
/// * Each tenure has 10 blocks (the coinbase block and 9 interim blocks)
/// * Verifies the block heights of the blocks mined
fn check_block_heights() {
if env::var("BITCOIND_TEST") != Ok("1".into()) {
return;
}
let signers = TestSigners::default();
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
let http_origin = format!("http://{}", &naka_conf.node.rpc_bind);
naka_conf.miner.wait_on_interim_blocks = Duration::from_secs(1);
let sender_sk = Secp256k1PrivateKey::new();
let sender_signer_sk = Secp256k1PrivateKey::new();
let sender_signer_addr = tests::to_addr(&sender_signer_sk);
let tenure_count = 5;
let inter_blocks_per_tenure = 9;
// setup sender + recipient for some test stx transfers
// these are necessary for the interim blocks to get mined at all
let sender_addr = tests::to_addr(&sender_sk);
let send_amt = 100;
let send_fee = 180;
let deploy_fee = 3000;
naka_conf.add_initial_balance(
PrincipalData::from(sender_addr.clone()).to_string(),
2 * deploy_fee + (send_amt + send_fee) * tenure_count * inter_blocks_per_tenure,
);
naka_conf.add_initial_balance(
PrincipalData::from(sender_signer_addr.clone()).to_string(),
100000,
);
let recipient = PrincipalData::from(StacksAddress::burn_address(false));
let stacker_sk = setup_stacker(&mut naka_conf);
test_observer::spawn();
let observer_port = test_observer::EVENT_OBSERVER_PORT;
naka_conf.events_observers.insert(EventObserverConfig {
endpoint: format!("localhost:{observer_port}"),
events_keys: vec![EventKeyType::AnyEvent],
});
let mut btcd_controller = BitcoinCoreController::new(naka_conf.clone());
btcd_controller
.start_bitcoind()
.expect("Failed starting bitcoind");
let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None);
btc_regtest_controller.bootstrap_chain(201);
let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap();
let run_loop_stopper = run_loop.get_termination_switch();
let Counters {
blocks_processed,
naka_submitted_vrfs: vrfs_submitted,
naka_submitted_commits: commits_submitted,
naka_proposed_blocks: proposals_submitted,
..
} = run_loop.counters();
let coord_channel = run_loop.coordinator_channels();
let run_loop_thread = thread::Builder::new()
.name("run_loop".into())
.spawn(move || run_loop.start(None, 0))
.unwrap();
wait_for_runloop(&blocks_processed);
boot_to_epoch_3(
&naka_conf,
&blocks_processed,
&[stacker_sk],
&[sender_signer_sk],
Some(&signers),
&mut btc_regtest_controller,
);
info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner");
let burnchain = naka_conf.get_burnchain();
let sortdb = burnchain.open_sortition_db(true).unwrap();
let (chainstate, _) = StacksChainState::open(
naka_conf.is_mainnet(),
naka_conf.burnchain.chain_id,
&naka_conf.get_chainstate_path_str(),
None,
)
.unwrap();
let block_height_pre_3_0 =
NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
.unwrap()
.unwrap()
.stacks_block_height;
info!("Nakamoto miner started...");
blind_signer(&naka_conf, &signers, proposals_submitted);
// first block wakes up the run loop, wait until a key registration has been submitted.
next_block_and(&mut btc_regtest_controller, 60, || {
let vrf_count = vrfs_submitted.load(Ordering::SeqCst);
Ok(vrf_count >= 1)
})
.unwrap();
// second block should confirm the VRF register, wait until a block commit is submitted
next_block_and(&mut btc_regtest_controller, 60, || {
let commits_count = commits_submitted.load(Ordering::SeqCst);
Ok(commits_count >= 1)
})
.unwrap();
let info = get_chain_info_result(&naka_conf).unwrap();
println!("Chain info: {:?}", info);
let mut last_burn_block_height = 0; // info.burn_block_height as u128;
let mut last_stacks_block_height = 0; // info.stacks_tip_height as u128;
let mut last_tenure_height = 0; // last_stacks_block_height as u128;
let mut sender_nonce = 0;
// This version uses the Clarity 1 / 2 keywords
let contract1_name = "test-contract-1";
let contract_clarity1 =
"(define-read-only (get-heights) { burn-block-height: burn-block-height, block-height: block-height })";
let contract_tx1 = make_contract_publish_versioned(
&sender_sk,
sender_nonce,
deploy_fee,
contract1_name,
contract_clarity1,
Some(ClarityVersion::Clarity2),
);
sender_nonce += 1;
submit_tx(&http_origin, &contract_tx1);
// This version uses the Clarity 3 keywords
let contract3_name = "test-contract-3";
let contract_clarity3 =
"(define-read-only (get-heights) { burn-block-height: burn-block-height, stacks-block-height: stacks-block-height, tenure-height: tenure-height })";
let contract_tx3 = make_contract_publish(
&sender_sk,
sender_nonce,
deploy_fee,
contract3_name,
contract_clarity3,
);
sender_nonce += 1;
submit_tx(&http_origin, &contract_tx3);
// Mine `tenure_count` nakamoto tenures
for tenure_ix in 0..tenure_count {
info!("Mining tenure {}", tenure_ix);
let commits_before = commits_submitted.load(Ordering::SeqCst);
next_block_and_process_new_stacks_block(&mut btc_regtest_controller, 60, &coord_channel)
.unwrap();
let heights1_value = call_read_only(
&naka_conf,
&sender_addr,
contract1_name,
"get-heights",
vec![],
);
let heights1 = heights1_value.expect_tuple().unwrap();
info!("Heights from Clarity 1: {}", heights1);
let heights3_value = call_read_only(
&naka_conf,
&sender_addr,
contract3_name,
"get-heights",
vec![],
);
let heights3 = heights3_value.expect_tuple().unwrap();
info!("Heights from Clarity 3: {}", heights3);
let bbh1 = heights1
.get("burn-block-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
let bbh3 = heights3
.get("burn-block-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
assert_eq!(bbh1, bbh3, "Burn block heights should match");
if last_burn_block_height == 0 {
last_burn_block_height = bbh1;
} else {
assert_eq!(
bbh1, last_burn_block_height,
"Burn block height should not have changed yet"
);
}
let bh1 = heights1
.get("block-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
let bh3 = heights3
.get("tenure-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
assert_eq!(
bh1, bh3,
"Clarity 2 block-height should match Clarity 3 tenure-height"
);
assert!(
bh1 > last_tenure_height,
"Tenure height should have incremented"
);
last_tenure_height = bh1;
let sbh = heights3
.get("stacks-block-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
assert!(
sbh > last_stacks_block_height,
"Stacks block heights should have incremented"
);
last_stacks_block_height = sbh;
// mine the interim blocks
for interim_block_ix in 0..inter_blocks_per_tenure {
info!("Mining interim block {interim_block_ix}");
let blocks_processed_before = coord_channel
.lock()
.expect("Mutex poisoned")
.get_stacks_blocks_processed();
// submit a tx so that the miner will mine an extra block
let transfer_tx =
make_stacks_transfer(&sender_sk, sender_nonce, send_fee, &recipient, send_amt);
sender_nonce += 1;
submit_tx(&http_origin, &transfer_tx);
loop {
let blocks_processed = coord_channel
.lock()
.expect("Mutex poisoned")
.get_stacks_blocks_processed();
if blocks_processed > blocks_processed_before {
break;
}
thread::sleep(Duration::from_millis(100));
}
let heights1_value = call_read_only(
&naka_conf,
&sender_addr,
contract1_name,
"get-heights",
vec![],
);
let heights1 = heights1_value.expect_tuple().unwrap();
info!("Heights from Clarity 1: {}", heights1);
let heights3_value = call_read_only(
&naka_conf,
&sender_addr,
contract3_name,
"get-heights",
vec![],
);
let heights3 = heights3_value.expect_tuple().unwrap();
info!("Heights from Clarity 3: {}", heights3);
let bbh1 = heights1
.get("burn-block-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
let bbh3 = heights3
.get("burn-block-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
assert_eq!(bbh1, bbh3, "Burn block heights should match");
if interim_block_ix == 0 {
assert!(
bbh1 > last_burn_block_height,
"Burn block heights should have incremented"
);
last_burn_block_height = bbh1;
} else {
assert_eq!(
bbh1, last_burn_block_height,
"Burn block heights should not have incremented"
);
}
let bh1 = heights1
.get("block-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
let bh3 = heights3
.get("tenure-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
assert_eq!(
bh1, bh3,
"Clarity 2 block-height should match Clarity 3 tenure-height"
);
assert_eq!(
bh1, last_tenure_height,
"Tenure height should not have changed"
);
let sbh = heights3
.get("stacks-block-height")
.unwrap()
.clone()
.expect_u128()
.unwrap();
assert!(
sbh > last_stacks_block_height,
"Stacks block heights should have incremented"
);
last_stacks_block_height = sbh;
}
let start_time = Instant::now();
while commits_submitted.load(Ordering::SeqCst) <= commits_before {
if start_time.elapsed() >= Duration::from_secs(20) {
panic!("Timed out waiting for block-commit");
}
thread::sleep(Duration::from_millis(100));
}
}
// load the chain tip, and assert that it is a nakamoto block and at least 30 blocks have advanced in epoch 3
let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
.unwrap()
.unwrap();
info!(
"Latest tip";
"height" => tip.stacks_block_height,
"is_nakamoto" => tip.anchored_header.as_stacks_nakamoto().is_some(),
);
assert!(tip.anchored_header.as_stacks_nakamoto().is_some());
assert_eq!(
tip.stacks_block_height,
block_height_pre_3_0 + ((inter_blocks_per_tenure + 1) * tenure_count),
"Should have mined (1 + interim_blocks_per_tenure) * tenure_count nakamoto blocks"
);
coord_channel
.lock()
.expect("Mutex poisoned")
.stop_chains_coordinator();
run_loop_stopper.store(false, Ordering::SeqCst);
run_loop_thread.join().unwrap();
}

View File

@@ -8,10 +8,12 @@ use std::{cmp, env, fs, io, thread};
use clarity::vm::ast::stack_depth_checker::AST_CALL_STACK_DEPTH_BUFFER;
use clarity::vm::ast::ASTRules;
use clarity::vm::costs::ExecutionCost;
use clarity::vm::types::serialization::SerializationError;
use clarity::vm::types::PrincipalData;
use clarity::vm::{ClarityName, ClarityVersion, ContractName, Value, MAX_CALL_STACK_DEPTH};
use rand::{Rng, RngCore};
use rusqlite::types::ToSql;
use serde::Deserialize;
use serde_json::json;
use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType};
use stacks::burnchains::bitcoin::BitcoinNetworkType;
@@ -856,6 +858,49 @@ pub fn get_tip_anchored_block(conf: &Config) -> (ConsensusHash, StacksBlock) {
(stacks_tip_consensus_hash, block)
}
#[derive(Deserialize, Debug)]
struct ReadOnlyResponse {
#[serde(rename = "okay")]
_okay: bool,
#[serde(rename = "result")]
result_hex: String,
}
impl ReadOnlyResponse {
pub fn result(&self) -> Result<Value, SerializationError> {
Value::try_deserialize_hex_untyped(&self.result_hex)
}
}
pub fn call_read_only(
conf: &Config,
principal: &StacksAddress,
contract: &str,
function: &str,
args: Vec<&str>,
) -> Value {
let http_origin = format!("http://{}", &conf.node.rpc_bind);
let client = reqwest::blocking::Client::new();
let path = format!(
"{http_origin}/v2/contracts/call-read/{}/{}/{}",
principal, contract, function
);
let body = json!({
"arguments": args,
"sender": principal.to_string(),
});
let response: ReadOnlyResponse = client
.post(path)
.header("Content-Type", "application/json")
.body(body.to_string())
.send()
.unwrap()
.json()
.unwrap();
response.result().unwrap()
}
fn find_microblock_privkey(
conf: &Config,
pubkey_hash: &Hash160,