Skip to content

Commit c033aad

Browse files
refactor!: Wrap fallible guesser fee helper functions in Result
Function `Block::total_guesser_reward`, along with other helper methods related to guesser fees, are undefined for un-validated blocks, so the result should be wrapped in a `Result` in order to avoid passing inconsistent values up the call graph. This change cascades into many function return type modifications and many `.unwrap`s and `.expect`s downstream. The inconsistency was exposed by test `rpc_server::rpc_server_tests::public_announcements_in_block_test` which is now fixed instead of ignored. Thanks to @skaunov for PR #543 and the effort leading to this exposition. Also: - Change logic for computing block proposal favorability: use `prev_block_digest` instead of block height. However, the old method (now suffixed with `_legacy`) continues to exist to enable unmodified processing of `PeerMessage` `BlockProposalNotification`, which comes with a block height and not a digest. (We cannot change `PeerMessage`, unfortunately.) - Rewrite handler for `PeerMessage` `BlockProposal`. Simplify and reduce indentation. Co-authored-by: Alan Szepieniec <[email protected]> Co-authored-by: Thorkil Schmidiger <[email protected]>
1 parent 8698d30 commit c033aad

File tree

21 files changed

+531
-265
lines changed

21 files changed

+531
-265
lines changed

benches/wallet_state.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ mod maintain_membership_proofs {
4141
rt.block_on(async {
4242
wallet_state
4343
.update_wallet_state_with_new_block(
44-
&genesis.mutator_set_accumulator_after(),
44+
&genesis.mutator_set_accumulator_after().unwrap(),
4545
&block1,
4646
)
4747
.await
@@ -64,7 +64,7 @@ mod maintain_membership_proofs {
6464
rt.block_on(async {
6565
wallet_state
6666
.update_wallet_state_with_new_block(
67-
&block1.mutator_set_accumulator_after(),
67+
&block1.mutator_set_accumulator_after().unwrap(),
6868
&block2,
6969
)
7070
.await

src/api/regtest/regtest_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl RegTestPrivate {
122122
SIZE_20MB_IN_BYTES,
123123
Some(MAX_NUM_TXS_TO_MERGE),
124124
true, //only_merge_single_proofs
125-
tip_block.mutator_set_accumulator_after().hash(),
125+
tip_block.mutator_set_accumulator_after().unwrap().hash(),
126126
);
127127

128128
drop(gs);

src/api/tx_initiation/builder/transaction_details_builder.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ impl TransactionDetailsBuilder {
257257
fee,
258258
coinbase,
259259
timestamp,
260-
tip_block.mutator_set_accumulator_after(),
260+
tip_block
261+
.mutator_set_accumulator_after()
262+
.expect("Block from state must be valid"),
261263
state_lock.cli().network,
262264
))
263265
}

src/bench_helpers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub async fn next_block_incoming_utxos(
8989
.sum::<NativeCurrencyAmount>()
9090
+ fee;
9191

92-
let msa = parent.mutator_set_accumulator_after();
92+
let msa = parent.mutator_set_accumulator_after().unwrap();
9393
let wallet_status = sender.get_wallet_status(parent.hash(), &msa).await;
9494
let change_amt = wallet_status
9595
.synced_unspent_available_amount(timestamp)

src/main_loop.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -893,8 +893,10 @@ impl MainLoopHandler {
893893
info!("Received new favorable block proposal for mining operation.");
894894
let mut global_state_mut = self.global_state_lock.lock_guard_mut().await;
895895
let verdict = global_state_mut.favor_incoming_block_proposal(
896-
block.header().height,
897-
block.total_guesser_reward(),
896+
block.header().prev_block_digest,
897+
block
898+
.total_guesser_reward()
899+
.expect("block received by main loop should be valid"),
898900
);
899901
if let Err(reject_reason) = verdict {
900902
warn!("main loop got unfavorable block proposal. Reason: {reject_reason}");

src/main_loop/proof_upgrader.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,8 @@ impl UpgradeJob {
380380
let tip_mutator_set = global_state
381381
.chain
382382
.light_state()
383-
.mutator_set_accumulator_after();
383+
.mutator_set_accumulator_after()
384+
.expect("Block from state must be valid");
384385

385386
let transaction_is_up_to_date =
386387
upgraded.kernel.mutator_set_hash == tip_mutator_set.hash();
@@ -740,7 +741,8 @@ pub(super) fn get_upgrade_task_from_mempool(
740741
let tip_mutator_set = global_state
741742
.chain
742743
.light_state()
743-
.mutator_set_accumulator_after();
744+
.mutator_set_accumulator_after()
745+
.expect("Block from state must be valid");
744746
let gobbling_fraction = global_state.gobbling_fraction();
745747
let min_gobbling_fee = global_state.min_gobbling_fee();
746748
let num_proofs_threshold = global_state.max_num_proofs();
@@ -1123,7 +1125,8 @@ mod tests {
11231125
.await
11241126
.chain
11251127
.light_state()
1126-
.mutator_set_accumulator_after();
1128+
.mutator_set_accumulator_after()
1129+
.unwrap();
11271130
assert!(mempool_tx.is_confirmable_relative_to(&mutator_set_accumulator_after));
11281131
}
11291132
}

src/mine_loop.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -527,13 +527,17 @@ pub(crate) fn composer_outputs(
527527

528528
/// Compute `TransactionDetails` and a list of `TxOutput`s for a coinbase
529529
/// transaction.
530+
///
531+
/// # Panics
532+
///
533+
/// - If `latest_block` is invalid.
530534
pub(super) fn prepare_coinbase_transaction_stateless(
531535
latest_block: &Block,
532536
composer_parameters: ComposerParameters,
533537
timestamp: Timestamp,
534538
network: Network,
535539
) -> (TxOutputList, TransactionDetails) {
536-
let mutator_set_accumulator = latest_block.mutator_set_accumulator_after().clone();
540+
let mutator_set_accumulator = latest_block.mutator_set_accumulator_after().unwrap();
537541
let next_block_height: BlockHeight = latest_block.header().height.next();
538542
info!("Creating coinbase for block of height {next_block_height}.");
539543

@@ -591,6 +595,8 @@ pub(crate) async fn create_block_transaction(
591595
.await
592596
}
593597

598+
/// # Panics
599+
/// - If predecessor is invalid
594600
pub(crate) async fn create_block_transaction_from(
595601
predecessor_block: &Block,
596602
global_state_lock: &GlobalStateLock,
@@ -600,7 +606,9 @@ pub(crate) async fn create_block_transaction_from(
600606
) -> Result<(Transaction, Vec<ExpectedUtxo>)> {
601607
let block_capacity_for_transactions = SIZE_20MB_IN_BYTES;
602608

603-
let predecessor_block_ms = predecessor_block.mutator_set_accumulator_after();
609+
let predecessor_block_ms = predecessor_block
610+
.mutator_set_accumulator_after()
611+
.expect("predecessor should be valid");
604612
let mutator_set_hash = predecessor_block_ms.hash();
605613
debug!("Creating block transaction with mutator set hash: {mutator_set_hash}",);
606614

@@ -645,7 +653,7 @@ pub(crate) async fn create_block_transaction_from(
645653
// Guarantees that some merge happens in below loop, which sets merge-bit.
646654
if transactions_to_merge.is_empty() {
647655
let nop = TransactionDetails::nop(
648-
predecessor_block.mutator_set_accumulator_after(),
656+
predecessor_block_ms,
649657
timestamp,
650658
global_state_lock.cli().network,
651659
);
@@ -1178,7 +1186,10 @@ pub(crate) mod tests {
11781186
make_mock_transaction_with_mutator_set_hash(
11791187
vec![],
11801188
outputs,
1181-
previous_block.mutator_set_accumulator_after().hash(),
1189+
previous_block
1190+
.mutator_set_accumulator_after()
1191+
.unwrap()
1192+
.hash(),
11821193
),
11831194
dummy_expected_utxo(),
11841195
)
@@ -1759,7 +1770,7 @@ pub(crate) mod tests {
17591770
let transaction = make_mock_transaction_with_mutator_set_hash(
17601771
vec![],
17611772
vec![],
1762-
prev_block.mutator_set_accumulator_after().hash(),
1773+
prev_block.mutator_set_accumulator_after().unwrap().hash(),
17631774
);
17641775

17651776
let guesser_key = HashLockKey::from_preimage(Digest::default());
@@ -2394,7 +2405,7 @@ pub(crate) mod tests {
23942405
let transaction = make_mock_transaction_with_mutator_set_hash(
23952406
vec![],
23962407
vec![],
2397-
prev_block.mutator_set_accumulator_after().hash(),
2408+
prev_block.mutator_set_accumulator_after().unwrap().hash(),
23982409
);
23992410

24002411
// gen guesser key

src/models/blockchain/block/mock_block_generator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl MockBlockGenerator {
146146
// create the nop-tx and merge into the coinbase transaction to set the
147147
// merge bit to allow the tx to be included in a block.
148148
let nop_details = TransactionDetails::nop(
149-
predecessor_block.mutator_set_accumulator_after(),
149+
predecessor_block.mutator_set_accumulator_after().unwrap(),
150150
timestamp,
151151
network,
152152
);

src/models/blockchain/block/mod.rs

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,16 @@ impl Block {
374374
///
375375
/// Includes the guesser-fee UTXOs which are not included by the
376376
/// `mutator_set_accumulator` field on the block body.
377-
pub fn mutator_set_accumulator_after(&self) -> MutatorSetAccumulator {
377+
pub fn mutator_set_accumulator_after(
378+
&self,
379+
) -> Result<MutatorSetAccumulator, BlockValidationError> {
378380
let mut msa = self.kernel.body.mutator_set_accumulator.clone();
379-
let mutator_set_update = MutatorSetUpdate::new(vec![], self.guesser_fee_addition_records());
381+
let mutator_set_update =
382+
MutatorSetUpdate::new(vec![], self.guesser_fee_addition_records()?);
380383
mutator_set_update.apply_to_accumulator(&mut msa)
381384
.expect("mutator set update derived from guesser fees should be applicable to mutator set accumulator contained in body");
382-
msa
385+
386+
Ok(msa)
383387
}
384388

385389
#[inline]
@@ -819,11 +823,9 @@ impl Block {
819823
}
820824

821825
// 2.a)
826+
let msa_before = previous_block.mutator_set_accumulator_after()?;
822827
for removal_record in &self.kernel.body.transaction_kernel.inputs {
823-
if !previous_block
824-
.mutator_set_accumulator_after()
825-
.can_remove(removal_record)
826-
{
828+
if !msa_before.can_remove(removal_record) {
827829
return Err(BlockValidationError::RemovalRecordsValid);
828830
}
829831
}
@@ -847,7 +849,7 @@ impl Block {
847849
self.body().transaction_kernel.inputs.clone(),
848850
self.body().transaction_kernel.outputs.clone(),
849851
);
850-
let mut msa = previous_block.mutator_set_accumulator_after();
852+
let mut msa = msa_before;
851853
let ms_update_result = mutator_set_update.apply_to_accumulator(&mut msa);
852854

853855
// 2.c)
@@ -1025,26 +1027,32 @@ impl Block {
10251027

10261028
/// The amount rewarded to the guesser who finds a valid nonce for this
10271029
/// block.
1028-
pub(crate) fn total_guesser_reward(&self) -> NativeCurrencyAmount {
1029-
self.body().transaction_kernel.fee
1030+
pub(crate) fn total_guesser_reward(
1031+
&self,
1032+
) -> Result<NativeCurrencyAmount, BlockValidationError> {
1033+
if self.body().transaction_kernel.fee.is_negative() {
1034+
return Err(BlockValidationError::NegativeFee);
1035+
}
1036+
1037+
Ok(self.body().transaction_kernel.fee)
10301038
}
10311039

10321040
/// Get the block's guesser fee UTXOs.
10331041
///
10341042
/// The amounts in the UTXOs are taken from the transaction fee.
10351043
///
10361044
/// The genesis block does not have a guesser reward.
1037-
pub(crate) fn guesser_fee_utxos(&self) -> Vec<Utxo> {
1045+
pub(crate) fn guesser_fee_utxos(&self) -> Result<Vec<Utxo>, BlockValidationError> {
10381046
const MINER_REWARD_TIME_LOCK_PERIOD: Timestamp = Timestamp::years(3);
10391047

10401048
if self.header().height.is_genesis() {
1041-
return vec![];
1049+
return Ok(vec![]);
10421050
}
10431051

10441052
let lock = self.header().guesser_digest;
10451053
let lock_script = HashLockKey::lock_script_from_after_image(lock);
10461054

1047-
let total_guesser_reward = self.total_guesser_reward();
1055+
let total_guesser_reward = self.total_guesser_reward()?;
10481056
let mut value_locked = total_guesser_reward;
10491057
value_locked.div_two();
10501058
let value_unlocked = total_guesser_reward.checked_sub(&value_locked).unwrap();
@@ -1056,15 +1064,18 @@ impl Block {
10561064
let locked_utxo = Utxo::new(lock_script.clone(), coins);
10571065
let unlocked_utxo = Utxo::new_native_currency(lock_script, value_unlocked);
10581066

1059-
vec![locked_utxo, unlocked_utxo]
1067+
Ok(vec![locked_utxo, unlocked_utxo])
10601068
}
10611069

10621070
/// Compute the addition records that correspond to the UTXOs generated for
10631071
/// the block's guesser
10641072
///
10651073
/// The genesis block does not have this addition record.
1066-
pub(crate) fn guesser_fee_addition_records(&self) -> Vec<AdditionRecord> {
1067-
self.guesser_fee_utxos()
1074+
pub(crate) fn guesser_fee_addition_records(
1075+
&self,
1076+
) -> Result<Vec<AdditionRecord>, BlockValidationError> {
1077+
Ok(self
1078+
.guesser_fee_utxos()?
10681079
.into_iter()
10691080
.map(|utxo| {
10701081
let item = Tip5::hash(&utxo);
@@ -1078,21 +1089,22 @@ impl Block {
10781089

10791090
commit(item, sender_randomness, receiver_digest)
10801091
})
1081-
.collect_vec()
1092+
.collect_vec())
10821093
}
10831094

10841095
/// Return the mutator set update corresponding to this block, which sends
10851096
/// the mutator set accumulator after the predecessor to the mutator set
10861097
/// accumulator after self.
1087-
pub(crate) fn mutator_set_update(&self) -> MutatorSetUpdate {
1098+
pub(crate) fn mutator_set_update(&self) -> Result<MutatorSetUpdate, BlockValidationError> {
10881099
let mut mutator_set_update = MutatorSetUpdate::new(
10891100
self.body().transaction_kernel.inputs.clone(),
10901101
self.body().transaction_kernel.outputs.clone(),
10911102
);
10921103

1093-
let extra_addition_records = self.guesser_fee_addition_records();
1104+
let extra_addition_records = self.guesser_fee_addition_records()?;
10941105
mutator_set_update.additions.extend(extra_addition_records);
1095-
mutator_set_update
1106+
1107+
Ok(mutator_set_update)
10961108
}
10971109
}
10981110

@@ -1245,7 +1257,7 @@ pub(crate) mod tests {
12451257
block_proof_witness.appendix(),
12461258
BlockProof::Invalid,
12471259
);
1248-
let total_guesser_reward = block1.total_guesser_reward();
1260+
let total_guesser_reward = block1.total_guesser_reward().unwrap();
12491261
let total_miner_reward = total_composer_reward + total_guesser_reward;
12501262
assert_eq!(NativeCurrencyAmount::coins(128), total_miner_reward);
12511263

@@ -1385,7 +1397,7 @@ pub(crate) mod tests {
13851397
}
13861398
}
13871399

1388-
#[proptest(async = "tokio")]
1400+
#[proptest(async = "tokio", cases = 5)]
13891401
async fn can_prove_block_ancestry(
13901402
#[strategy(collection::vec(arb::<Digest>(), 55))] mut sender_randomness_vec: Vec<Digest>,
13911403
#[strategy(0..54usize)] index: usize,
@@ -1813,9 +1825,10 @@ pub(crate) mod tests {
18131825
(0.4, guesser_preimage),
18141826
)
18151827
.await;
1816-
let ars = block1.guesser_fee_addition_records();
1828+
let ars = block1.guesser_fee_addition_records().unwrap();
18171829
let ars_from_wallet = block1
18181830
.guesser_fee_utxos()
1831+
.unwrap()
18191832
.iter()
18201833
.map(|utxo| commit(Tip5::hash(utxo), block1.hash(), guesser_preimage.hash()))
18211834
.collect_vec();
@@ -1824,7 +1837,7 @@ pub(crate) mod tests {
18241837
let MutatorSetUpdate {
18251838
removals: _,
18261839
additions,
1827-
} = block1.mutator_set_update();
1840+
} = block1.mutator_set_update().unwrap();
18281841
assert!(
18291842
ars.iter().all(|ar| additions.contains(ar)),
18301843
"All addition records must be present in block's mutator set update"
@@ -1845,7 +1858,7 @@ pub(crate) mod tests {
18451858
let preimage = rand::rng().random::<Digest>();
18461859
block.set_header_guesser_digest(preimage.hash());
18471860

1848-
let guesser_fee_utxos = block.guesser_fee_utxos();
1861+
let guesser_fee_utxos = block.guesser_fee_utxos().unwrap();
18491862

18501863
let lock_script_and_witness =
18511864
HashLockKey::from_preimage(preimage).lock_script_and_witness();
@@ -1868,7 +1881,7 @@ pub(crate) mod tests {
18681881
let network = Network::Main;
18691882
let genesis_block = Block::genesis(network);
18701883
assert!(
1871-
genesis_block.guesser_fee_utxos().is_empty(),
1884+
genesis_block.guesser_fee_utxos().unwrap().is_empty(),
18721885
"Genesis block has no guesser fee UTXOs"
18731886
);
18741887

@@ -1932,7 +1945,7 @@ pub(crate) mod tests {
19321945
let mut ms = block1.body().mutator_set_accumulator.clone();
19331946

19341947
let mutator_set_update_guesser_fees =
1935-
MutatorSetUpdate::new(vec![], block1.guesser_fee_addition_records());
1948+
MutatorSetUpdate::new(vec![], block1.guesser_fee_addition_records().unwrap());
19361949
let mut mutator_set_update_tx = MutatorSetUpdate::new(
19371950
block2.body().transaction_kernel.inputs.clone(),
19381951
block2.body().transaction_kernel.outputs.clone(),

0 commit comments

Comments
 (0)