From 1fbbdf1db03115b9bb50162d18a5f985fc97dd5a Mon Sep 17 00:00:00 2001 From: Brice Dobry Date: Mon, 17 Jun 2024 13:26:59 -0400 Subject: [PATCH] test: add test for `block-reward` property This is separated out as a "slow" test because it requires mining 100 blocks to wait for the rewards to mature. --- .github/workflows/slow-tests.yml | 1 + .../src/tests/nakamoto_integrations.rs | 344 ++++++++++++++++++ 2 files changed, 345 insertions(+) diff --git a/.github/workflows/slow-tests.yml b/.github/workflows/slow-tests.yml index bce6a15a1f..02c5bdf552 100644 --- a/.github/workflows/slow-tests.yml +++ b/.github/workflows/slow-tests.yml @@ -32,6 +32,7 @@ jobs: test-name: - tests::epoch_21::test_pox_reorg_flap_duel - tests::epoch_21::test_pox_reorg_flap_reward_cycles + - tests::nakamoto_integrations::check_block_info_rewards steps: ## Setup test environment - name: Setup Test Environment diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 59f1f3c1ca..c75f18d619 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -5956,3 +5956,347 @@ fn check_block_info() { run_loop_thread.join().unwrap(); } + +#[test] +#[ignore] +/// Verify `block-reward` property in `get-block-info?` and `get-tenure-info?`. +/// This test is separated from `check_block_info` above because it needs to +/// mine 100+ blocks to mature the block reward, so it is slow. +fn check_block_info_rewards() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let mut 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); + + // 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(), + 3 * deploy_fee + (send_amt + send_fee) * 2, + ); + 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); + + let mut sender_nonce = 0; + + // Deploy this version with the Clarity 1 / 2 before epoch 3 + let contract0_name = "test-contract-0"; + let contract_clarity1 = "(define-read-only (get-info (height uint)) + { + burnchain-header-hash: (get-block-info? burnchain-header-hash height), + id-header-hash: (get-block-info? id-header-hash height), + header-hash: (get-block-info? header-hash height), + miner-address: (get-block-info? miner-address height), + time: (get-block-info? time height), + vrf-seed: (get-block-info? vrf-seed height), + block-reward: (get-block-info? block-reward height), + miner-spend-total: (get-block-info? miner-spend-total height), + miner-spend-winner: (get-block-info? miner-spend-winner height), + } + )"; + + let contract_tx0 = make_contract_publish( + &sender_sk, + sender_nonce, + deploy_fee, + contract0_name, + contract_clarity1, + ); + sender_nonce += 1; + submit_tx(&http_origin, &contract_tx0); + + boot_to_epoch_3( + &naka_conf, + &blocks_processed, + &[stacker_sk], + &[sender_signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); + + info!("Nakamoto miner started..."); + blind_signer(&naka_conf, &signers, proposals_submitted); + + let result0 = call_read_only( + &naka_conf, + &sender_addr, + contract0_name, + "get-info", + vec![&clarity::vm::Value::UInt(1)], + ); + let tuple0 = result0.expect_tuple().unwrap().data_map; + info!("Info from pre-epoch 3.0: {:?}", tuple0); + + // 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(); + + // This version uses the Clarity 1 / 2 function + let contract1_name = "test-contract-1"; + 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 functions + let contract3_name = "test-contract-3"; + let contract_clarity3 = "(define-read-only (get-tenure-info (height uint)) + { + burnchain-header-hash: (get-tenure-info? burnchain-header-hash height), + miner-address: (get-tenure-info? miner-address height), + time: (get-tenure-info? time height), + vrf-seed: (get-tenure-info? vrf-seed height), + block-reward: (get-tenure-info? block-reward height), + miner-spend-total: (get-tenure-info? miner-spend-total height), + miner-spend-winner: (get-tenure-info? miner-spend-winner 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); + + next_block_and_process_new_stacks_block(&mut btc_regtest_controller, 60, &coord_channel) + .unwrap(); + + // Sleep to ensure the seconds have changed + thread::sleep(Duration::from_secs(1)); + + // Mine a Nakamoto block + info!("Mining Nakamoto block"); + 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)); + } + + // Sleep to ensure the seconds have changed + thread::sleep(Duration::from_secs(1)); + + // Mine a Nakamoto block + info!("Mining Nakamoto block"); + 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); + 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 info = get_chain_info_result(&naka_conf).unwrap(); + info!("Chain info: {:?}", info); + let last_stacks_block_height = info.stacks_tip_height as u128; + + // Mining 100 blocks takes a while, so only run this test if the env var is set + let last_nakamoto_block = last_stacks_block_height; + + // Mine 100+ burn blocks to get the block reward matured + info!("Mining 102 tenures to mature the block reward"); + for i in 0..102 { + let commits_before = commits_submitted.load(Ordering::SeqCst); + next_block_and(&mut btc_regtest_controller, 60, || { + let commits_count = commits_submitted.load(Ordering::SeqCst); + Ok(commits_count >= commits_before + 1) + }) + .unwrap(); + info!("Mined a block ({i})"); + } + + let info = get_chain_info_result(&naka_conf).unwrap(); + info!("Chain info: {:?}", info); + let last_stacks_block_height = info.stacks_tip_height as u128; + + // Check the block reward is now matured in one of the tenure-change blocks + let result0 = call_read_only( + &naka_conf, + &sender_addr, + contract0_name, + "get-info", + vec![&clarity::vm::Value::UInt(last_stacks_block_height - 100)], + ); + let tuple0 = result0.expect_tuple().unwrap().data_map; + assert_eq!( + tuple0 + .get("block-reward") + .unwrap() + .clone() + .expect_optional() + .unwrap() + .unwrap(), + Value::UInt(2040806360) + ); + + let result1 = call_read_only( + &naka_conf, + &sender_addr, + contract1_name, + "get-info", + vec![&clarity::vm::Value::UInt(last_stacks_block_height - 100)], + ); + let tuple1 = result1.expect_tuple().unwrap().data_map; + assert_eq!(tuple0, tuple1); + + let result3_tenure = call_read_only( + &naka_conf, + &sender_addr, + contract3_name, + "get-tenure-info", + vec![&clarity::vm::Value::UInt(last_stacks_block_height - 100)], + ); + let tuple3_tenure = result3_tenure.expect_tuple().unwrap().data_map; + assert_eq!( + tuple3_tenure.get("block-reward"), + tuple0.get("block-reward") + ); + + // Check the block reward is now matured in one of the Nakamoto blocks + let result0 = call_read_only( + &naka_conf, + &sender_addr, + contract0_name, + "get-info", + vec![&clarity::vm::Value::UInt(last_nakamoto_block)], + ); + let tuple0 = result0.expect_tuple().unwrap().data_map; + assert_eq!( + tuple0 + .get("block-reward") + .unwrap() + .clone() + .expect_optional() + .unwrap() + .unwrap(), + Value::UInt(3061200000) + ); + + let result1 = call_read_only( + &naka_conf, + &sender_addr, + contract1_name, + "get-info", + vec![&clarity::vm::Value::UInt(last_nakamoto_block)], + ); + let tuple1 = result1.expect_tuple().unwrap().data_map; + assert_eq!(tuple0, tuple1); + + let result3_tenure = call_read_only( + &naka_conf, + &sender_addr, + contract3_name, + "get-tenure-info", + vec![&clarity::vm::Value::UInt(last_nakamoto_block)], + ); + let tuple3_tenure = result3_tenure.expect_tuple().unwrap().data_map; + assert_eq!( + tuple3_tenure.get("block-reward"), + tuple0.get("block-reward") + ); + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +}