Skip to content

Commit 3e01ab9

Browse files
committed
Merge branch 'develop' of https://github.com/stacks-network/stacks-core into feat/clippy-ci-stacks-common
2 parents 2bb2686 + 0ee5d7c commit 3e01ab9

File tree

6 files changed

+264
-17
lines changed

6 files changed

+264
-17
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ jobs:
144144
- tests::nakamoto_integrations::mock_mining
145145
- tests::nakamoto_integrations::multiple_miners
146146
- tests::nakamoto_integrations::follower_bootup_across_multiple_cycles
147+
- tests::nakamoto_integrations::nakamoto_lockup_events
147148
- tests::nakamoto_integrations::utxo_check_on_startup_panic
148149
- tests::nakamoto_integrations::utxo_check_on_startup_recover
149150
- tests::nakamoto_integrations::v3_signer_api_endpoint

stackslib/src/chainstate/nakamoto/mod.rs

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ use super::stacks::db::{
7373
use super::stacks::events::{StacksTransactionReceipt, TransactionOrigin};
7474
use super::stacks::{
7575
Error as ChainstateError, StacksBlock, StacksBlockHeader, StacksMicroblock, StacksTransaction,
76-
TenureChangeError, TenureChangePayload, TransactionPayload,
76+
TenureChangeError, TenureChangePayload, TokenTransferMemo, TransactionPayload,
77+
TransactionVersion,
7778
};
7879
use crate::burnchains::{Burnchain, PoxConstants, Txid};
7980
use crate::chainstate::burn::db::sortdb::SortitionDB;
@@ -108,8 +109,7 @@ use crate::core::{
108109
};
109110
use crate::net::stackerdb::{StackerDBConfig, MINER_SLOT_COUNT};
110111
use crate::net::Error as net_error;
111-
use crate::util_lib::boot;
112-
use crate::util_lib::boot::boot_code_id;
112+
use crate::util_lib::boot::{self, boot_code_addr, boot_code_id, boot_code_tx_auth};
113113
use crate::util_lib::db::{
114114
query_int, query_row, query_row_columns, query_row_panic, query_rows, sqlite_open,
115115
tx_begin_immediate, u64_to_sql, DBConn, Error as DBError, FromRow,
@@ -2093,7 +2093,8 @@ impl NakamotoChainState {
20932093
return Err(e);
20942094
};
20952095

2096-
let (receipt, clarity_commit, reward_set_data) = ok_opt.expect("FATAL: unreachable");
2096+
let (mut receipt, clarity_commit, reward_set_data, phantom_unlock_events) =
2097+
ok_opt.expect("FATAL: unreachable");
20972098

20982099
assert_eq!(
20992100
receipt.header.anchored_header.block_hash(),
@@ -2147,6 +2148,20 @@ impl NakamotoChainState {
21472148
&receipt.header.anchored_header.block_hash()
21482149
);
21492150

2151+
let tx_receipts = &mut receipt.tx_receipts;
2152+
if let Some(unlock_receipt) =
2153+
// For the event dispatcher, attach any STXMintEvents that
2154+
// could not be included in the block (e.g. because the
2155+
// block didn't have a Coinbase transaction).
2156+
Self::generate_phantom_unlock_tx(
2157+
phantom_unlock_events,
2158+
&stacks_chain_state.config(),
2159+
next_ready_block.header.chain_length,
2160+
)
2161+
{
2162+
tx_receipts.push(unlock_receipt);
2163+
}
2164+
21502165
// announce the block, if we're connected to an event dispatcher
21512166
if let Some(dispatcher) = dispatcher_opt {
21522167
let block_event = (
@@ -2157,7 +2172,7 @@ impl NakamotoChainState {
21572172
dispatcher.announce_block(
21582173
&block_event,
21592174
&receipt.header.clone(),
2160-
&receipt.tx_receipts,
2175+
&tx_receipts,
21612176
&parent_block_id,
21622177
next_ready_block_snapshot.winning_block_txid,
21632178
&receipt.matured_rewards,
@@ -4193,11 +4208,13 @@ impl NakamotoChainState {
41934208
applied_epoch_transition: bool,
41944209
signers_updated: bool,
41954210
coinbase_height: u64,
4211+
phantom_lockup_events: Vec<StacksTransactionEvent>,
41964212
) -> Result<
41974213
(
41984214
StacksEpochReceipt,
41994215
PreCommitClarityBlock<'a>,
42004216
Option<RewardSetData>,
4217+
Vec<StacksTransactionEvent>,
42014218
),
42024219
ChainstateError,
42034220
> {
@@ -4234,7 +4251,7 @@ impl NakamotoChainState {
42344251
coinbase_height,
42354252
};
42364253

4237-
return Ok((epoch_receipt, clarity_commit, None));
4254+
return Ok((epoch_receipt, clarity_commit, None, phantom_lockup_events));
42384255
}
42394256

42404257
/// Append a Nakamoto Stacks block to the Stacks chain state.
@@ -4260,6 +4277,7 @@ impl NakamotoChainState {
42604277
StacksEpochReceipt,
42614278
PreCommitClarityBlock<'a>,
42624279
Option<RewardSetData>,
4280+
Vec<StacksTransactionEvent>,
42634281
),
42644282
ChainstateError,
42654283
> {
@@ -4527,18 +4545,20 @@ impl NakamotoChainState {
45274545
Ok(lockup_events) => lockup_events,
45284546
};
45294547

4530-
// if any, append lockups events to the coinbase receipt
4531-
if lockup_events.len() > 0 {
4548+
// If any, append lockups events to the coinbase receipt
4549+
if let Some(receipt) = tx_receipts.get_mut(0) {
45324550
// Receipts are appended in order, so the first receipt should be
45334551
// the one of the coinbase transaction
4534-
if let Some(receipt) = tx_receipts.get_mut(0) {
4535-
if receipt.is_coinbase_tx() {
4536-
receipt.events.append(&mut lockup_events);
4537-
}
4538-
} else {
4539-
warn!("Unable to attach lockups events, block's first transaction is not a coinbase transaction")
4552+
if receipt.is_coinbase_tx() {
4553+
receipt.events.append(&mut lockup_events);
45404554
}
45414555
}
4556+
4557+
// If lockup_events still contains items, it means they weren't attached
4558+
if !lockup_events.is_empty() {
4559+
info!("Unable to attach lockup events, block's first transaction is not a coinbase transaction. Will attach as a phantom tx.");
4560+
}
4561+
45424562
// if any, append auto unlock events to the coinbase receipt
45434563
if auto_unlock_events.len() > 0 {
45444564
// Receipts are appended in order, so the first receipt should be
@@ -4611,6 +4631,7 @@ impl NakamotoChainState {
46114631
applied_epoch_transition,
46124632
signer_set_calc.is_some(),
46134633
coinbase_height,
4634+
lockup_events,
46144635
);
46154636
}
46164637

@@ -4724,7 +4745,12 @@ impl NakamotoChainState {
47244745
coinbase_height,
47254746
};
47264747

4727-
Ok((epoch_receipt, clarity_commit, reward_set_data))
4748+
Ok((
4749+
epoch_receipt,
4750+
clarity_commit,
4751+
reward_set_data,
4752+
lockup_events,
4753+
))
47284754
}
47294755

47304756
/// Create a StackerDB config for the .miners contract.
@@ -4885,6 +4911,53 @@ impl NakamotoChainState {
48854911
clarity.save_analysis(&contract_id, &analysis).unwrap();
48864912
})
48874913
}
4914+
4915+
/// Generate a "phantom" transaction to include STXMintEvents for
4916+
/// lockups that could not be attached to a Coinbase transaction
4917+
/// (because the block doesn't have a Coinbase transaction).
4918+
fn generate_phantom_unlock_tx(
4919+
events: Vec<StacksTransactionEvent>,
4920+
config: &ChainstateConfig,
4921+
stacks_block_height: u64,
4922+
) -> Option<StacksTransactionReceipt> {
4923+
if events.is_empty() {
4924+
return None;
4925+
}
4926+
info!("Generating phantom unlock tx");
4927+
let version = if config.mainnet {
4928+
TransactionVersion::Mainnet
4929+
} else {
4930+
TransactionVersion::Testnet
4931+
};
4932+
4933+
// Make the txid unique -- the phantom tx payload should include something block-specific otherwise
4934+
// they will always have the same txid. In this case we use the block height in the memo. This also
4935+
// happens to give some indication of the purpose of this phantom tx, for anyone looking.
4936+
let memo = TokenTransferMemo({
4937+
let str = format!("Block {} token unlocks", stacks_block_height);
4938+
let mut buf = [0u8; 34];
4939+
buf[..str.len().min(34)].copy_from_slice(&str.as_bytes()[..]);
4940+
buf
4941+
});
4942+
let boot_code_address = boot_code_addr(config.mainnet);
4943+
let boot_code_auth = boot_code_tx_auth(boot_code_address.clone());
4944+
let unlock_tx = StacksTransaction::new(
4945+
version,
4946+
boot_code_auth,
4947+
TransactionPayload::TokenTransfer(
4948+
PrincipalData::Standard(boot_code_address.into()),
4949+
0,
4950+
memo,
4951+
),
4952+
);
4953+
let unlock_receipt = StacksTransactionReceipt::from_stx_transfer(
4954+
unlock_tx,
4955+
events,
4956+
Value::okay_true(),
4957+
ExecutionCost::ZERO,
4958+
);
4959+
Some(unlock_receipt)
4960+
}
48884961
}
48894962

48904963
impl StacksMessageCodec for NakamotoBlock {

stackslib/src/chainstate/stacks/db/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2903,7 +2903,7 @@ pub mod test {
29032903
// Just update the expected value
29042904
assert_eq!(
29052905
genesis_root_hash.to_string(),
2906-
"c771616ff6acb710051238c9f4a3c48020a6d70cda637d34b89f2311a7e27886"
2906+
"0eb3076f0635ccdfcdc048afb8dea9048c5180a2e2b2952874af1d18f06321e8"
29072907
);
29082908
}
29092909

stx-genesis/chainstate-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,5 @@ SM1ZH700J7CEDSEHM5AJ4C4MKKWNESTS35DD3SZM5,13888889,2267
6969
SM260QHD6ZM2KKPBKZB8PFE5XWP0MHSKTD1B7BHYR,208333333,45467
7070
SM260QHD6ZM2KKPBKZB8PFE5XWP0MHSKTD1B7BHYR,208333333,6587
7171
SM260QHD6ZM2KKPBKZB8PFE5XWP0MHSKTD1B7BHYR,208333333,2267
72+
SP2CTPPV8BHBVSQR727A3MK00ZD85RNY903KAG9F3,12345678,35
7273
-----END STX VESTING-----
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
014402b47d53b0716402c172fa746adf308b03a826ebea91944a5eb6a304a823
1+
088c3caea982a8f6f74dda48ec5f06f51f7605def9760a971b1acd763ee6b7cf

0 commit comments

Comments
 (0)