@@ -73,7 +73,8 @@ use super::stacks::db::{
73
73
use super :: stacks:: events:: { StacksTransactionReceipt , TransactionOrigin } ;
74
74
use super :: stacks:: {
75
75
Error as ChainstateError , StacksBlock , StacksBlockHeader , StacksMicroblock , StacksTransaction ,
76
- TenureChangeError , TenureChangePayload , TransactionPayload ,
76
+ TenureChangeError , TenureChangePayload , TokenTransferMemo , TransactionPayload ,
77
+ TransactionVersion ,
77
78
} ;
78
79
use crate :: burnchains:: { Burnchain , PoxConstants , Txid } ;
79
80
use crate :: chainstate:: burn:: db:: sortdb:: SortitionDB ;
@@ -108,8 +109,7 @@ use crate::core::{
108
109
} ;
109
110
use crate :: net:: stackerdb:: { StackerDBConfig , MINER_SLOT_COUNT } ;
110
111
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} ;
113
113
use crate :: util_lib:: db:: {
114
114
query_int, query_row, query_row_columns, query_row_panic, query_rows, sqlite_open,
115
115
tx_begin_immediate, u64_to_sql, DBConn , Error as DBError , FromRow ,
@@ -2093,7 +2093,8 @@ impl NakamotoChainState {
2093
2093
return Err ( e) ;
2094
2094
} ;
2095
2095
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" ) ;
2097
2098
2098
2099
assert_eq ! (
2099
2100
receipt. header. anchored_header. block_hash( ) ,
@@ -2147,6 +2148,20 @@ impl NakamotoChainState {
2147
2148
& receipt. header. anchored_header. block_hash( )
2148
2149
) ;
2149
2150
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
+
2150
2165
// announce the block, if we're connected to an event dispatcher
2151
2166
if let Some ( dispatcher) = dispatcher_opt {
2152
2167
let block_event = (
@@ -2157,7 +2172,7 @@ impl NakamotoChainState {
2157
2172
dispatcher. announce_block (
2158
2173
& block_event,
2159
2174
& receipt. header . clone ( ) ,
2160
- & receipt . tx_receipts ,
2175
+ & tx_receipts,
2161
2176
& parent_block_id,
2162
2177
next_ready_block_snapshot. winning_block_txid ,
2163
2178
& receipt. matured_rewards ,
@@ -4193,11 +4208,13 @@ impl NakamotoChainState {
4193
4208
applied_epoch_transition : bool ,
4194
4209
signers_updated : bool ,
4195
4210
coinbase_height : u64 ,
4211
+ phantom_lockup_events : Vec < StacksTransactionEvent > ,
4196
4212
) -> Result <
4197
4213
(
4198
4214
StacksEpochReceipt ,
4199
4215
PreCommitClarityBlock < ' a > ,
4200
4216
Option < RewardSetData > ,
4217
+ Vec < StacksTransactionEvent > ,
4201
4218
) ,
4202
4219
ChainstateError ,
4203
4220
> {
@@ -4234,7 +4251,7 @@ impl NakamotoChainState {
4234
4251
coinbase_height,
4235
4252
} ;
4236
4253
4237
- return Ok ( ( epoch_receipt, clarity_commit, None ) ) ;
4254
+ return Ok ( ( epoch_receipt, clarity_commit, None , phantom_lockup_events ) ) ;
4238
4255
}
4239
4256
4240
4257
/// Append a Nakamoto Stacks block to the Stacks chain state.
@@ -4260,6 +4277,7 @@ impl NakamotoChainState {
4260
4277
StacksEpochReceipt ,
4261
4278
PreCommitClarityBlock < ' a > ,
4262
4279
Option < RewardSetData > ,
4280
+ Vec < StacksTransactionEvent > ,
4263
4281
) ,
4264
4282
ChainstateError ,
4265
4283
> {
@@ -4527,18 +4545,20 @@ impl NakamotoChainState {
4527
4545
Ok ( lockup_events) => lockup_events,
4528
4546
} ;
4529
4547
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 ) {
4532
4550
// Receipts are appended in order, so the first receipt should be
4533
4551
// 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) ;
4540
4554
}
4541
4555
}
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
+
4542
4562
// if any, append auto unlock events to the coinbase receipt
4543
4563
if auto_unlock_events. len ( ) > 0 {
4544
4564
// Receipts are appended in order, so the first receipt should be
@@ -4611,6 +4631,7 @@ impl NakamotoChainState {
4611
4631
applied_epoch_transition,
4612
4632
signer_set_calc. is_some ( ) ,
4613
4633
coinbase_height,
4634
+ lockup_events,
4614
4635
) ;
4615
4636
}
4616
4637
@@ -4724,7 +4745,12 @@ impl NakamotoChainState {
4724
4745
coinbase_height,
4725
4746
} ;
4726
4747
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
+ ) )
4728
4754
}
4729
4755
4730
4756
/// Create a StackerDB config for the .miners contract.
@@ -4885,6 +4911,53 @@ impl NakamotoChainState {
4885
4911
clarity. save_analysis ( & contract_id, & analysis) . unwrap ( ) ;
4886
4912
} )
4887
4913
}
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
+ }
4888
4961
}
4889
4962
4890
4963
impl StacksMessageCodec for NakamotoBlock {
0 commit comments