Skip to content

Commit 283b215

Browse files
Merge pull request #355 from mintlayer/fix/read_block_reward
Avoid reading the whole block to get the block reward
2 parents a691e1b + 751ac13 commit 283b215

File tree

10 files changed

+163
-12
lines changed

10 files changed

+163
-12
lines changed

chainstate-storage/src/internal/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod utxo_db;
1818
use chainstate_types::BlockIndex;
1919
use common::{
2020
chain::{
21+
block::BlockReward,
2122
transaction::{Transaction, TxMainChainIndex, TxMainChainPosition},
2223
Block, GenBlock, OutPoint, OutPointSourceId,
2324
},
@@ -141,6 +142,7 @@ impl<B: for<'tx> traits::Transactional<'tx, Schema>> BlockchainStorageRead for S
141142
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;
142143
fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
143144
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
145+
fn get_block_reward(&self, block_index: &BlockIndex) -> crate::Result<Option<BlockReward>>;
144146

145147
fn get_mainchain_tx_index(
146148
&self,
@@ -226,6 +228,22 @@ impl<Tx: for<'a> traits::GetMapRef<'a, Schema>> BlockchainStorageRead for StoreT
226228
self.read::<DBBlock, _, _>(id.as_ref())
227229
}
228230

231+
fn get_block_reward(&self, block_index: &BlockIndex) -> crate::Result<Option<BlockReward>> {
232+
match self.0.get::<DBBlock, _>().get(block_index.block_id().as_ref()) {
233+
Err(e) => Err(e.into()),
234+
Ok(None) => Ok(None),
235+
Ok(Some(block)) => {
236+
let header_size = block_index.block_header().encoded_size();
237+
let begin = header_size;
238+
let encoded_block_reward_begin =
239+
block.get(begin..).expect("Block reward outside of block range");
240+
let block_reward = BlockReward::decode(&mut &*encoded_block_reward_begin)
241+
.expect("Invalid block reward encoding in DB");
242+
Ok(Some(block_reward))
243+
}
244+
}
245+
}
246+
229247
fn get_mainchain_tx_index(
230248
&self,
231249
tx_id: &OutPointSourceId,

chainstate-storage/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//! Application-level interface for the persistent blockchain storage.
1717
1818
use chainstate_types::BlockIndex;
19+
use common::chain::block::BlockReward;
1920
use common::chain::transaction::{Transaction, TxMainChainIndex, TxMainChainPosition};
2021
use common::chain::OutPointSourceId;
2122
use common::chain::{Block, GenBlock};
@@ -49,6 +50,8 @@ pub trait BlockchainStorageRead: UtxosStorageRead {
4950

5051
fn get_block_index(&self, block_id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
5152

53+
fn get_block_reward(&self, block_index: &BlockIndex) -> crate::Result<Option<BlockReward>>;
54+
5255
/// Get block by its hash
5356
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
5457

chainstate-storage/src/mock.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use chainstate_types::BlockIndex;
1919
use common::{
2020
chain::{
21+
block::BlockReward,
2122
transaction::{OutPointSourceId, Transaction, TxMainChainIndex, TxMainChainPosition},
2223
Block, GenBlock, OutPoint,
2324
},
@@ -37,6 +38,7 @@ mockall::mock! {
3738
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;
3839
fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
3940
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
41+
fn get_block_reward(&self, block_index: &BlockIndex) -> crate::Result<Option<BlockReward>>;
4042

4143
fn get_mainchain_tx_index(
4244
&self,
@@ -113,6 +115,7 @@ mockall::mock! {
113115
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;
114116
fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
115117
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
118+
fn get_block_reward(&self, block_index: &BlockIndex) -> crate::Result<Option<BlockReward>>;
116119

117120
fn get_mainchain_tx_index(
118121
&self,
@@ -151,6 +154,7 @@ mockall::mock! {
151154
fn get_best_block_id(&self) -> crate::Result<Option<Id<GenBlock>>>;
152155
fn get_block(&self, id: Id<Block>) -> crate::Result<Option<Block>>;
153156
fn get_block_index(&self, id: &Id<Block>) -> crate::Result<Option<BlockIndex>>;
157+
fn get_block_reward(&self, block_index: &BlockIndex) -> crate::Result<Option<BlockReward>>;
154158

155159
fn get_mainchain_tx_index(
156160
&self,

chainstate/src/detail/ban_score.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ impl BanScore for ConnectTransactionError {
9292
ConnectTransactionError::InvariantErrorTransactionCouldNotBeLoaded(_) => 100,
9393
// Even though this is an invariant error, it stems from a block reward that doesn't exist
9494
ConnectTransactionError::InvariantErrorHeaderCouldNotBeLoaded(_) => 100,
95+
ConnectTransactionError::InvariantErrorBlockIndexCouldNotBeLoaded(_) => 100,
9596
ConnectTransactionError::InvariantErrorBlockCouldNotBeLoaded(_) => 100,
9697
ConnectTransactionError::FailedToAddAllFeesOfBlock(_) => 100,
9798
ConnectTransactionError::RewardAdditionError(_) => 100,

chainstate/src/detail/chainstateref.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use chainstate_storage::{BlockchainStorageRead, BlockchainStorageWrite, Transact
1919
use chainstate_types::{get_skip_height, BlockIndex, GenBlockIndex, PropertyQueryError};
2020
use common::{
2121
chain::{
22-
block::{calculate_tx_merkle_root, calculate_witness_merkle_root, BlockHeader},
22+
block::{
23+
calculate_tx_merkle_root, calculate_witness_merkle_root, BlockHeader, BlockReward,
24+
},
2325
Block, ChainConfig, GenBlock, GenBlockId, OutPointSourceId,
2426
},
2527
primitives::{BlockDistance, BlockHeight, Id, Idable},
@@ -53,19 +55,28 @@ impl<'a, S: BlockchainStorageRead, O: OrphanBlocks> BlockIndexHandle for Chainst
5355
) -> Result<Option<BlockIndex>, PropertyQueryError> {
5456
self.get_block_index(block_id)
5557
}
58+
5659
fn get_gen_block_index(
5760
&self,
5861
block_id: &Id<GenBlock>,
5962
) -> Result<Option<GenBlockIndex>, PropertyQueryError> {
6063
self.get_gen_block_index(block_id)
6164
}
65+
6266
fn get_ancestor(
6367
&self,
6468
block_index: &BlockIndex,
6569
ancestor_height: BlockHeight,
6670
) -> Result<GenBlockIndex, PropertyQueryError> {
6771
self.get_ancestor(&GenBlockIndex::Block(block_index.clone()), ancestor_height)
6872
}
73+
74+
fn get_block_reward(
75+
&self,
76+
block_index: &BlockIndex,
77+
) -> Result<Option<BlockReward>, PropertyQueryError> {
78+
self.get_block_reward(block_index)
79+
}
6980
}
7081

7182
impl<'a, S: BlockchainStorageRead, O: OrphanBlocks> TransactionIndexHandle
@@ -308,6 +319,13 @@ impl<'a, S: BlockchainStorageRead, O: OrphanBlocks> ChainstateRef<'a, S, O> {
308319
Ok(self.get_block_index(&id)?.map(|block_index| block_index.into_block_header()))
309320
}
310321

322+
pub fn get_block_reward(
323+
&self,
324+
block_index: &BlockIndex,
325+
) -> Result<Option<BlockReward>, PropertyQueryError> {
326+
Ok(self.db_tx.get_block_reward(block_index)?)
327+
}
328+
311329
pub fn get_block_height_in_main_chain(
312330
&self,
313331
id: &Id<GenBlock>,

chainstate/src/detail/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ use itertools::Itertools;
3333
use chainstate_storage::{BlockchainStorage, Transactional};
3434
use chainstate_types::{BlockIndex, GenBlockIndex, PropertyQueryError};
3535
use common::{
36-
chain::{block::BlockHeader, config::ChainConfig, Block, GenBlock},
36+
chain::{
37+
block::{BlockHeader, BlockReward},
38+
config::ChainConfig,
39+
Block, GenBlock,
40+
},
3741
primitives::{BlockDistance, BlockHeight, Id, Idable},
3842
};
3943
use logging::log;
@@ -293,6 +297,14 @@ impl<S: BlockchainStorage> Chainstate<S> {
293297
self.make_db_tx_ro().get_best_block_id()
294298
}
295299

300+
#[allow(dead_code)]
301+
pub fn get_block_reward(
302+
&self,
303+
block_index: &BlockIndex,
304+
) -> Result<Option<BlockReward>, PropertyQueryError> {
305+
self.make_db_tx_ro().get_block_reward(block_index)
306+
}
307+
296308
#[allow(dead_code)]
297309
pub fn get_header_from_height(
298310
&self,

chainstate/src/detail/tests/processing_tests.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use common::{
2626
primitives::Compact,
2727
Uint256,
2828
};
29-
use consensus::{ConsensusPoWError, ConsensusVerificationError};
29+
use consensus::{BlockIndexHandle, ConsensusPoWError, ConsensusVerificationError};
3030
use crypto::key::{KeyKind, PrivateKey};
3131

3232
use crate::{
@@ -639,6 +639,88 @@ fn pow(#[case] seed: Seed) {
639639
tf.process_block(valid_block.clone(), BlockSource::Local).unwrap();
640640
}
641641

642+
#[rstest]
643+
#[trace]
644+
#[case(Seed::from_entropy())]
645+
fn read_block_reward_from_storage(#[case] seed: Seed) {
646+
let mut rng = make_seedable_rng(seed);
647+
let ignore_consensus = BlockHeight::new(0);
648+
let pow_consensus = BlockHeight::new(1);
649+
let difficulty =
650+
Uint256([0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x0FFFFFFFFFFFFFFF]);
651+
652+
let upgrades = vec![
653+
(
654+
ignore_consensus,
655+
UpgradeVersion::ConsensusUpgrade(ConsensusUpgrade::IgnoreConsensus),
656+
),
657+
(
658+
pow_consensus,
659+
UpgradeVersion::ConsensusUpgrade(ConsensusUpgrade::PoW {
660+
initial_difficulty: difficulty.into(),
661+
}),
662+
),
663+
];
664+
665+
let net_upgrades = NetUpgrades::initialize(upgrades).expect("valid netupgrades");
666+
// Internally this calls Consensus::new, which processes the genesis block
667+
// This should succeed because TestChainConfig by default uses create_mainnet_genesis to
668+
// create the genesis_block, and this function creates a genesis block with
669+
// ConsensusData::None, which agrees with the net_upgrades we defined above.
670+
let chain_config = ConfigBuilder::test_chain().net_upgrades(net_upgrades).build();
671+
let mut tf = TestFramework::builder().with_chain_config(chain_config).build();
672+
673+
let block_reward_output_count = rng.gen::<usize>() % 20;
674+
let expected_block_reward = (0..block_reward_output_count)
675+
.map(|_| {
676+
let amount = Amount::from_atoms(rng.gen::<u128>() % 50);
677+
let pub_key = PrivateKey::new(KeyKind::RistrettoSchnorr).1;
678+
TxOutput::new(
679+
OutputValue::Coin(amount),
680+
OutputPurpose::Transfer(Destination::PublicKey(pub_key)),
681+
)
682+
})
683+
.collect::<Vec<_>>();
684+
685+
// We generate a PoW block, then use its reward to test the storage of block rewards
686+
let block = {
687+
let mut random_invalid_block = tf
688+
.make_block_builder()
689+
.with_reward(expected_block_reward.clone())
690+
.add_test_transaction(&mut rng)
691+
.build();
692+
make_invalid_pow_block(&mut random_invalid_block, u128::MAX, difficulty.into())
693+
.expect("generate invalid block");
694+
695+
let mut valid_block = random_invalid_block;
696+
let bits = difficulty.into();
697+
assert!(consensus::pow::mine(&mut valid_block, u128::MAX, bits)
698+
.expect("Unexpected conversion error"));
699+
valid_block
700+
};
701+
tf.process_block(block, BlockSource::Local).unwrap();
702+
703+
let block_index = tf.chainstate.get_best_block_index().unwrap().unwrap();
704+
let block_index = match block_index {
705+
GenBlockIndex::Block(bi) => bi,
706+
GenBlockIndex::Genesis(_) => unreachable!(),
707+
};
708+
709+
{
710+
let block_reward = tf.chainstate.get_block_reward(&block_index).unwrap().unwrap();
711+
712+
assert_eq!(block_reward.outputs(), expected_block_reward);
713+
}
714+
715+
{
716+
let block_index_handle = &tf.chainstate.make_db_tx_ro() as &dyn BlockIndexHandle;
717+
718+
let block_reward = block_index_handle.get_block_reward(&block_index).unwrap().unwrap();
719+
720+
assert_eq!(block_reward.outputs(), expected_block_reward);
721+
}
722+
}
723+
642724
#[test]
643725
fn blocks_from_the_future() {
644726
common::concurrency::model(|| {

chainstate/src/detail/transaction_verifier/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ pub enum ConnectTransactionError {
7474
InvariantErrorTransactionCouldNotBeLoaded(TxMainChainPosition),
7575
#[error("Transaction index for header found but header not found")]
7676
InvariantErrorHeaderCouldNotBeLoaded(Id<Block>),
77+
#[error("Unable to find block index")]
78+
InvariantErrorBlockIndexCouldNotBeLoaded(Id<Block>),
7779
#[error("Unable to find block")]
7880
InvariantErrorBlockCouldNotBeLoaded(Id<Block>),
7981
#[error("Addition of all fees in block `{0}` failed")]

chainstate/src/detail/transaction_verifier/mod.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -644,14 +644,19 @@ impl<'a, S: BlockchainStorageRead> TransactionVerifier<'a, S> {
644644
.to_vec()),
645645
// TODO: Getting the whole block just for reward outputs isn't optimal. See the
646646
// https://github.com/mintlayer/mintlayer-core/issues/344 issue for details.
647-
GenBlockId::Block(id) => Ok(self
648-
.db_tx
649-
.get_block(id)?
650-
.ok_or(ConnectTransactionError::InvariantErrorBlockCouldNotBeLoaded(id))?
651-
.block_reward_transactable()
652-
.outputs()
653-
.unwrap_or(&[])
654-
.to_vec()),
647+
GenBlockId::Block(id) => {
648+
let block_index = self
649+
.db_tx
650+
.get_block_index(&id)?
651+
.ok_or(ConnectTransactionError::InvariantErrorBlockIndexCouldNotBeLoaded(id))?;
652+
let reward = self
653+
.db_tx
654+
.get_block_reward(&block_index)?
655+
.ok_or(ConnectTransactionError::InvariantErrorBlockCouldNotBeLoaded(id))?
656+
.outputs()
657+
.to_vec();
658+
Ok(reward)
659+
}
655660
}
656661
}
657662

consensus/src/validator/block_index_handle.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
use chainstate_types::{BlockIndex, GenBlockIndex, PropertyQueryError};
1717
use common::{
18-
chain::{Block, GenBlock},
18+
chain::{block::BlockReward, Block, GenBlock},
1919
primitives::{BlockHeight, Id},
2020
};
2121

@@ -39,4 +39,10 @@ pub trait BlockIndexHandle {
3939
block_index: &BlockIndex,
4040
ancestor_height: BlockHeight,
4141
) -> Result<GenBlockIndex, PropertyQueryError>;
42+
43+
/// Returns the block reward of the given block
44+
fn get_block_reward(
45+
&self,
46+
block_index: &BlockIndex,
47+
) -> Result<Option<BlockReward>, PropertyQueryError>;
4248
}

0 commit comments

Comments
 (0)