From 9e53963989347427b87504e13d324e95f3d75e51 Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Thu, 14 Nov 2024 19:36:23 -0800 Subject: [PATCH 1/8] feat: include lockup events in nakamoto blocks without coinbase --- .github/workflows/bitcoin-tests.yml | 1 + stackslib/src/chainstate/nakamoto/mod.rs | 77 +++++++- stackslib/src/cli.rs | 3 +- stx-genesis/chainstate-test.txt | 1 + stx-genesis/chainstate-test.txt.sha256 | 2 +- .../src/tests/nakamoto_integrations.rs | 167 ++++++++++++++++++ 6 files changed, 241 insertions(+), 10 deletions(-) diff --git a/.github/workflows/bitcoin-tests.yml b/.github/workflows/bitcoin-tests.yml index 52f46fbc49..956fa3382b 100644 --- a/.github/workflows/bitcoin-tests.yml +++ b/.github/workflows/bitcoin-tests.yml @@ -136,6 +136,7 @@ jobs: - tests::nakamoto_integrations::mock_mining - tests::nakamoto_integrations::multiple_miners - tests::nakamoto_integrations::follower_bootup_across_multiple_cycles + - tests::nakamoto_integrations::nakamoto_lockup_events - tests::nakamoto_integrations::utxo_check_on_startup_panic - tests::nakamoto_integrations::utxo_check_on_startup_recover - tests::nakamoto_integrations::v3_signer_api_endpoint diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index d67de8e987..1d39248656 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -73,7 +73,8 @@ use super::stacks::db::{ use super::stacks::events::{StacksTransactionReceipt, TransactionOrigin}; use super::stacks::{ Error as ChainstateError, StacksBlock, StacksBlockHeader, StacksMicroblock, StacksTransaction, - TenureChangeError, TenureChangePayload, TransactionPayload, + TenureChangeError, TenureChangePayload, TokenTransferMemo, TransactionPayload, + TransactionVersion, }; use crate::burnchains::{Burnchain, PoxConstants, Txid}; use crate::chainstate::burn::db::sortdb::SortitionDB; @@ -108,8 +109,7 @@ use crate::core::{ }; use crate::net::stackerdb::{StackerDBConfig, MINER_SLOT_COUNT}; use crate::net::Error as net_error; -use crate::util_lib::boot; -use crate::util_lib::boot::boot_code_id; +use crate::util_lib::boot::{self, boot_code_addr, boot_code_id, boot_code_tx_auth}; use crate::util_lib::db::{ query_int, query_row, query_row_columns, query_row_panic, query_rows, sqlite_open, tx_begin_immediate, u64_to_sql, DBConn, Error as DBError, FromRow, @@ -2048,7 +2048,8 @@ impl NakamotoChainState { return Err(e); }; - let (receipt, clarity_commit, reward_set_data) = ok_opt.expect("FATAL: unreachable"); + let (receipt, clarity_commit, reward_set_data, phantom_unlock_events) = + ok_opt.expect("FATAL: unreachable"); assert_eq!( receipt.header.anchored_header.block_hash(), @@ -2102,6 +2103,19 @@ impl NakamotoChainState { &receipt.header.anchored_header.block_hash() ); + let mut tx_receipts = receipt.tx_receipts.clone(); + if let Some(unlock_receipt) = + // For the event dispatcher, attach any STXMintEvents that + // could not be included in the block (e.g. because the + // block didn't have a Coinbase transaction). + Self::generate_phantom_unlock_tx( + phantom_unlock_events, + &stacks_chain_state.config(), + ) + { + tx_receipts.push(unlock_receipt); + } + // announce the block, if we're connected to an event dispatcher if let Some(dispatcher) = dispatcher_opt { let block_event = ( @@ -2112,7 +2126,7 @@ impl NakamotoChainState { dispatcher.announce_block( &block_event, &receipt.header.clone(), - &receipt.tx_receipts, + &tx_receipts, &parent_block_id, next_ready_block_snapshot.winning_block_txid, &receipt.matured_rewards, @@ -3915,6 +3929,7 @@ impl NakamotoChainState { StacksEpochReceipt, PreCommitClarityBlock<'a>, Option, + Vec, ), ChainstateError, > { @@ -4215,6 +4230,8 @@ impl NakamotoChainState { Ok(lockup_events) => lockup_events, }; + // Track events that we couldn't attach to a coinbase receipt + let mut phantom_lockup_events = lockup_events.clone(); // if any, append lockups events to the coinbase receipt if lockup_events.len() > 0 { // Receipts are appended in order, so the first receipt should be @@ -4222,11 +4239,14 @@ impl NakamotoChainState { if let Some(receipt) = tx_receipts.get_mut(0) { if receipt.is_coinbase_tx() { receipt.events.append(&mut lockup_events); + phantom_lockup_events.clear(); } - } else { - warn!("Unable to attach lockups events, block's first transaction is not a coinbase transaction") } } + if phantom_lockup_events.len() > 0 { + info!("Unable to attach lockup events, block's first transaction is not a coinbase transaction. Will attach as a phantom tx."); + } + // if any, append auto unlock events to the coinbase receipt if auto_unlock_events.len() > 0 { // Receipts are appended in order, so the first receipt should be @@ -4394,7 +4414,12 @@ impl NakamotoChainState { coinbase_height, }; - Ok((epoch_receipt, clarity_commit, reward_set_data)) + Ok(( + epoch_receipt, + clarity_commit, + reward_set_data, + phantom_lockup_events, + )) } /// Create a StackerDB config for the .miners contract. @@ -4555,6 +4580,42 @@ impl NakamotoChainState { clarity.save_analysis(&contract_id, &analysis).unwrap(); }) } + + /// Generate a "phantom" transaction to include STXMintEvents for + /// lockups that could not be attached to a Coinbase transaction + /// (because the block doesn't have a Coinbase transaction). + fn generate_phantom_unlock_tx( + events: Vec, + config: &ChainstateConfig, + ) -> Option { + if events.is_empty() { + return None; + } + info!("Generating phantom unlock tx"); + let version = if config.mainnet { + TransactionVersion::Mainnet + } else { + TransactionVersion::Testnet + }; + let boot_code_address = boot_code_addr(config.mainnet); + let boot_code_auth = boot_code_tx_auth(boot_code_address.clone()); + let unlock_tx = StacksTransaction::new( + version, + boot_code_auth, + TransactionPayload::TokenTransfer( + PrincipalData::Standard(boot_code_address.into()), + 0, + TokenTransferMemo([0u8; 34]), + ), + ); + let unlock_receipt = StacksTransactionReceipt::from_stx_transfer( + unlock_tx, + events, + Value::okay_true(), + ExecutionCost::zero(), + ); + Some(unlock_receipt) + } } impl StacksMessageCodec for NakamotoBlock { diff --git a/stackslib/src/cli.rs b/stackslib/src/cli.rs index 587daee787..b463641a3d 100644 --- a/stackslib/src/cli.rs +++ b/stackslib/src/cli.rs @@ -783,7 +783,8 @@ fn replay_block_nakamoto( return Err(e); }; - let (receipt, _clarity_commit, _reward_set_data) = ok_opt.expect("FATAL: unreachable"); + let (receipt, _clarity_commit, _reward_set_data, _phantom_events) = + ok_opt.expect("FATAL: unreachable"); assert_eq!( receipt.header.anchored_header.block_hash(), diff --git a/stx-genesis/chainstate-test.txt b/stx-genesis/chainstate-test.txt index 614cf3d9f4..6eedf241d1 100644 --- a/stx-genesis/chainstate-test.txt +++ b/stx-genesis/chainstate-test.txt @@ -69,4 +69,5 @@ SM1ZH700J7CEDSEHM5AJ4C4MKKWNESTS35DD3SZM5,13888889,2267 SM260QHD6ZM2KKPBKZB8PFE5XWP0MHSKTD1B7BHYR,208333333,45467 SM260QHD6ZM2KKPBKZB8PFE5XWP0MHSKTD1B7BHYR,208333333,6587 SM260QHD6ZM2KKPBKZB8PFE5XWP0MHSKTD1B7BHYR,208333333,2267 +SP2CTPPV8BHBVSQR727A3MK00ZD85RNY903KAG9F3,12345678,35 -----END STX VESTING----- \ No newline at end of file diff --git a/stx-genesis/chainstate-test.txt.sha256 b/stx-genesis/chainstate-test.txt.sha256 index 56782ae494..69ac95c254 100644 --- a/stx-genesis/chainstate-test.txt.sha256 +++ b/stx-genesis/chainstate-test.txt.sha256 @@ -1 +1 @@ -014402b47d53b0716402c172fa746adf308b03a826ebea91944a5eb6a304a823 \ No newline at end of file +088c3caea982a8f6f74dda48ec5f06f51f7605def9760a971b1acd763ee6b7cf \ No newline at end of file diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 6ae34fce42..8af87a56c9 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -9301,6 +9301,173 @@ fn v3_signer_api_endpoint() { run_loop_thread.join().unwrap(); } +/// Verify that lockup events are attached to a phantom tx receipt +/// if the block does not have a coinbase tx +#[test] +#[ignore] +fn nakamoto_lockup_events() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let (mut conf, _miner_account) = naka_neon_integration_conf(None); + let password = "12345".to_string(); + conf.connection_options.auth_token = Some(password.clone()); + conf.miner.wait_on_interim_blocks = Duration::from_secs(1); + let stacker_sk = setup_stacker(&mut conf); + let signer_sk = Secp256k1PrivateKey::new(); + let signer_addr = tests::to_addr(&signer_sk); + let _signer_pubkey = Secp256k1PublicKey::from_private(&signer_sk); + let sender_sk = Secp256k1PrivateKey::new(); + // 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; + conf.add_initial_balance( + PrincipalData::from(sender_addr).to_string(), + (send_amt + send_fee) * 100, + ); + conf.add_initial_balance(PrincipalData::from(signer_addr).to_string(), 100000); + let recipient = PrincipalData::from(StacksAddress::burn_address(false)); + + // only subscribe to the block proposal events + test_observer::spawn(); + test_observer::register_any(&mut conf); + + let mut btcd_controller = BitcoinCoreController::new(conf.clone()); + btcd_controller + .start_bitcoind() + .expect("Failed starting bitcoind"); + let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None); + btc_regtest_controller.bootstrap_chain(201); + + let mut run_loop = boot_nakamoto::BootRunLoop::new(conf.clone()).unwrap(); + let run_loop_stopper = run_loop.get_termination_switch(); + let Counters { + blocks_processed, + 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::spawn(move || run_loop.start(None, 0)); + let mut signers = TestSigners::new(vec![signer_sk]); + wait_for_runloop(&blocks_processed); + boot_to_epoch_3( + &conf, + &blocks_processed, + &[stacker_sk], + &[signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("------------------------- Reached Epoch 3.0 -------------------------"); + blind_signer(&conf, &signers, proposals_submitted); + let burnchain = conf.get_burnchain(); + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let (chainstate, _) = StacksChainState::open( + conf.is_mainnet(), + conf.burnchain.chain_id, + &conf.get_chainstate_path_str(), + None, + ) + .unwrap(); + // TODO (hack) instantiate the sortdb in the burnchain + _ = btc_regtest_controller.sortdb_mut(); + + info!("------------------------- Setup finished, run test -------------------------"); + + next_block_and_mine_commit( + &mut btc_regtest_controller, + 60, + &coord_channel, + &commits_submitted, + ) + .unwrap(); + + let http_origin = format!("http://{}", &conf.node.rpc_bind); + + let get_stacks_height = || { + let tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb) + .unwrap() + .unwrap(); + tip.stacks_block_height + }; + let initial_block_height = get_stacks_height(); + + // This matches the data in `stx-genesis/chainstate-test.txt` + // Recipient: ST2CTPPV8BHBVSQR727A3MK00ZD85RNY9015WGW2D + let unlock_recipient = "ST2CTPPV8BHBVSQR727A3MK00ZD85RNY9015WGW2D"; + let unlock_height = 35_u64; + let interims_to_mine = unlock_height - initial_block_height; + + info!( + "----- Mining to unlock height -----"; + "unlock_height" => unlock_height, + "initial_height" => initial_block_height, + "interims_to_mine" => interims_to_mine, + ); + + // submit a tx so that the miner will mine an extra stacks block + let mut sender_nonce = 0; + + for _ in 0..interims_to_mine { + let height_before = get_stacks_height(); + info!("----- Mining interim block -----"; + "height" => %height_before, + "nonce" => %sender_nonce, + ); + let transfer_tx = make_stacks_transfer( + &sender_sk, + sender_nonce, + send_fee, + conf.burnchain.chain_id, + &recipient, + send_amt, + ); + submit_tx(&http_origin, &transfer_tx); + sender_nonce += 1; + + wait_for(30, || Ok(get_stacks_height() > height_before)).unwrap(); + } + + let blocks = test_observer::get_blocks(); + let block = blocks.last().unwrap(); + assert_eq!( + block.get("block_height").unwrap().as_u64().unwrap(), + unlock_height + ); + + let events = block.get("events").unwrap().as_array().unwrap(); + let mut found_event = false; + for event in events { + let mint_event = event.get("stx_mint_event"); + if mint_event.is_some() { + found_event = true; + let mint_event = mint_event.unwrap(); + let recipient = mint_event.get("recipient").unwrap().as_str().unwrap(); + assert_eq!(recipient, unlock_recipient); + let amount = mint_event.get("amount").unwrap().as_str().unwrap(); + assert_eq!(amount, "12345678"); + } + } + assert!(found_event); + + info!("------------------------- Test finished, clean up -------------------------"); + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +} + #[test] #[ignore] /// This test spins up a nakamoto-neon node. From 1f4f6dff87c1bfe9dcd986c1cc44630c1a4b4684 Mon Sep 17 00:00:00 2001 From: Hank Stoever Date: Fri, 15 Nov 2024 05:10:44 -0800 Subject: [PATCH 2/8] feat: assert that txid is deterministic --- testnet/stacks-node/src/tests/nakamoto_integrations.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 8af87a56c9..4078fabc93 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -9453,6 +9453,11 @@ fn nakamoto_lockup_events() { assert_eq!(recipient, unlock_recipient); let amount = mint_event.get("amount").unwrap().as_str().unwrap(); assert_eq!(amount, "12345678"); + let txid = event.get("txid").unwrap().as_str().unwrap(); + assert_eq!( + txid, + "0xcba511741b230bd85cb5b3b10d26e0b92695d4a83f95c260cad82a40cd764235" + ); } } assert!(found_event); From 29baa895898fffb2e89b6b941a91abc68c7393c2 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 19 Dec 2024 11:56:44 -0700 Subject: [PATCH 3/8] chore: fix errors after merge --- stackslib/src/chainstate/nakamoto/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 71068e1519..7be4b2d89b 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -4207,11 +4207,13 @@ impl NakamotoChainState { applied_epoch_transition: bool, signers_updated: bool, coinbase_height: u64, + phantom_lockup_events: Vec, ) -> Result< ( StacksEpochReceipt, PreCommitClarityBlock<'a>, Option, + Vec, ), ChainstateError, > { @@ -4248,7 +4250,7 @@ impl NakamotoChainState { coinbase_height, }; - return Ok((epoch_receipt, clarity_commit, None)); + return Ok((epoch_receipt, clarity_commit, None, phantom_lockup_events)); } /// Append a Nakamoto Stacks block to the Stacks chain state. @@ -4631,6 +4633,7 @@ impl NakamotoChainState { applied_epoch_transition, signer_set_calc.is_some(), coinbase_height, + phantom_lockup_events, ); } @@ -4942,7 +4945,7 @@ impl NakamotoChainState { unlock_tx, events, Value::okay_true(), - ExecutionCost::zero(), + ExecutionCost::ZERO, ); Some(unlock_receipt) } From 41921b9b4de41d5f306fdb335e6f9dd2d3beacd3 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 19 Dec 2024 12:33:30 -0700 Subject: [PATCH 4/8] fix: include block height in phantom tx memo for unique txids --- stackslib/src/chainstate/nakamoto/mod.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 7be4b2d89b..0694edf7a0 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -2156,6 +2156,7 @@ impl NakamotoChainState { Self::generate_phantom_unlock_tx( phantom_unlock_events, &stacks_chain_state.config(), + next_ready_block.header.chain_length, ) { tx_receipts.push(unlock_receipt); @@ -4920,6 +4921,7 @@ impl NakamotoChainState { fn generate_phantom_unlock_tx( events: Vec, config: &ChainstateConfig, + stacks_block_height: u64, ) -> Option { if events.is_empty() { return None; @@ -4930,6 +4932,16 @@ impl NakamotoChainState { } else { TransactionVersion::Testnet }; + + // Make the txid unique -- the phantom tx payload should include something block-specific otherwise + // they will always have the same txid. In this case we use the block height in the memo. This also + // happens to give some indication of the purpose of this phantom tx, for anyone looking. + let memo = TokenTransferMemo({ + let str = format!("Block {} token unlocks", stacks_block_height); + let mut buf = [0u8; 34]; + buf[..str.len().min(34)].copy_from_slice(&str.as_bytes()[..]); + buf + }); let boot_code_address = boot_code_addr(config.mainnet); let boot_code_auth = boot_code_tx_auth(boot_code_address.clone()); let unlock_tx = StacksTransaction::new( @@ -4938,7 +4950,7 @@ impl NakamotoChainState { TransactionPayload::TokenTransfer( PrincipalData::Standard(boot_code_address.into()), 0, - TokenTransferMemo([0u8; 34]), + memo, ), ); let unlock_receipt = StacksTransactionReceipt::from_stx_transfer( From ce10a2b56f0ae8b12bb1f8574614f105f4cbefa1 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 19 Dec 2024 13:07:58 -0700 Subject: [PATCH 5/8] chore: fix test --- testnet/stacks-node/src/tests/nakamoto_integrations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index d84aa5d578..13923a847a 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -9577,7 +9577,7 @@ fn nakamoto_lockup_events() { let txid = event.get("txid").unwrap().as_str().unwrap(); assert_eq!( txid, - "0xcba511741b230bd85cb5b3b10d26e0b92695d4a83f95c260cad82a40cd764235" + "0x63dd5773338782755e4947a05a336539137dfe13b19a0eac5154306850aca8ef" ); } } From 76552d4439dfb82275b69bd7ebdf81c9eccd81b4 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 19 Dec 2024 14:16:55 -0700 Subject: [PATCH 6/8] chore: fix tests --- stackslib/src/chainstate/stacks/db/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/chainstate/stacks/db/mod.rs b/stackslib/src/chainstate/stacks/db/mod.rs index 42f72d5165..ffdea5a7dd 100644 --- a/stackslib/src/chainstate/stacks/db/mod.rs +++ b/stackslib/src/chainstate/stacks/db/mod.rs @@ -2903,7 +2903,7 @@ pub mod test { // Just update the expected value assert_eq!( genesis_root_hash.to_string(), - "c771616ff6acb710051238c9f4a3c48020a6d70cda637d34b89f2311a7e27886" + "0eb3076f0635ccdfcdc048afb8dea9048c5180a2e2b2952874af1d18f06321e8" ); } From 346bc93b4f817d36badfa427beb0fff0b87cb7b9 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 19 Dec 2024 15:00:19 -0700 Subject: [PATCH 7/8] fix: remove clone --- stackslib/src/chainstate/nakamoto/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 0694edf7a0..edb10cd4d7 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -2093,7 +2093,7 @@ impl NakamotoChainState { return Err(e); }; - let (receipt, clarity_commit, reward_set_data, phantom_unlock_events) = + let (mut receipt, clarity_commit, reward_set_data, phantom_unlock_events) = ok_opt.expect("FATAL: unreachable"); assert_eq!( @@ -2148,7 +2148,7 @@ impl NakamotoChainState { &receipt.header.anchored_header.block_hash() ); - let mut tx_receipts = receipt.tx_receipts.clone(); + let tx_receipts = &mut receipt.tx_receipts; if let Some(unlock_receipt) = // For the event dispatcher, attach any STXMintEvents that // could not be included in the block (e.g. because the From f01b42716f34aafebe4aaba8d7d92df5b7955c21 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 19 Dec 2024 15:19:33 -0700 Subject: [PATCH 8/8] chore: move another clone --- stackslib/src/chainstate/nakamoto/mod.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index edb10cd4d7..929d8dfe90 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -4545,20 +4545,17 @@ impl NakamotoChainState { Ok(lockup_events) => lockup_events, }; - // Track events that we couldn't attach to a coinbase receipt - let mut phantom_lockup_events = lockup_events.clone(); - // if any, append lockups events to the coinbase receipt - if lockup_events.len() > 0 { + // If any, append lockups events to the coinbase receipt + if let Some(receipt) = tx_receipts.get_mut(0) { // Receipts are appended in order, so the first receipt should be // the one of the coinbase transaction - if let Some(receipt) = tx_receipts.get_mut(0) { - if receipt.is_coinbase_tx() { - receipt.events.append(&mut lockup_events); - phantom_lockup_events.clear(); - } + if receipt.is_coinbase_tx() { + receipt.events.append(&mut lockup_events); } } - if phantom_lockup_events.len() > 0 { + + // If lockup_events still contains items, it means they weren't attached + if !lockup_events.is_empty() { info!("Unable to attach lockup events, block's first transaction is not a coinbase transaction. Will attach as a phantom tx."); } @@ -4634,7 +4631,7 @@ impl NakamotoChainState { applied_epoch_transition, signer_set_calc.is_some(), coinbase_height, - phantom_lockup_events, + lockup_events, ); } @@ -4752,7 +4749,7 @@ impl NakamotoChainState { epoch_receipt, clarity_commit, reward_set_data, - phantom_lockup_events, + lockup_events, )) }