Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce proptest in chainstate #294

Merged
merged 12 commits into from
Jul 26, 2022
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions chainstate-storage/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! A mock version of the blockchian storage.
//! A mock version of the blockchain storage.
azarovh marked this conversation as resolved.
Show resolved Hide resolved

use chainstate_types::block_index::BlockIndex;
use common::chain::block::Block;
Expand Down Expand Up @@ -83,7 +83,7 @@ mockall::mock! {
}

mockall::mock! {
/// A mock object for blockcain storage transaction
/// A mock object for blockchain storage transaction
pub StoreTxRo {}

impl crate::BlockchainStorageRead for StoreTxRo {
Expand Down Expand Up @@ -115,7 +115,7 @@ mockall::mock! {
}

mockall::mock! {
/// A mock object for blockcain storage transaction
/// A mock object for blockchain storage transaction
pub StoreTxRw {}

impl crate::BlockchainStorageRead for StoreTxRw {
Expand Down
2 changes: 2 additions & 0 deletions chainstate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ mockall = "0.11"
serde_json = "1.0"
static_assertions = "1.1"
tokio = "1.19"
rand_chacha = "0.3.1"
hex = "0.4"
4 changes: 2 additions & 2 deletions chainstate/src/chainstate_interface_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ use crate::{
ChainstateError, ChainstateEvent, ChainstateInterface, Locator,
};

pub struct ChainstateInterfaceImpl {
pub(crate) struct ChainstateInterfaceImpl {
chainstate: detail::Chainstate,
}

impl ChainstateInterfaceImpl {
pub fn new(chainstate: detail::Chainstate) -> Self {
pub(crate) fn new(chainstate: detail::Chainstate) -> Self {
Self { chainstate }
}
}
Expand Down
39 changes: 22 additions & 17 deletions chainstate/src/detail/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub mod time_getter;
use time_getter::TimeGetter;

#[must_use]
pub struct Chainstate {
pub(crate) struct Chainstate {
chain_config: Arc<ChainConfig>,
chainstate_config: ChainstateConfig,
chainstate_storage: chainstate_storage::Store,
Expand All @@ -74,7 +74,8 @@ pub enum BlockSource {
}

impl Chainstate {
pub fn wait_for_all_events(&self) {
#[allow(dead_code)]
azarovh marked this conversation as resolved.
Show resolved Hide resolved
iljakuklic marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn wait_for_all_events(&self) {
azarovh marked this conversation as resolved.
Show resolved Hide resolved
self.events_controller.wait_for_all_events();
}

Expand Down Expand Up @@ -102,11 +103,11 @@ impl Chainstate {
)
}

pub fn subscribe_to_events(&mut self, handler: ChainstateEventHandler) {
pub(crate) fn subscribe_to_events(&mut self, handler: ChainstateEventHandler) {
self.events_controller.subscribe_to_events(handler);
}

pub fn new(
pub(crate) fn new(
chain_config: Arc<ChainConfig>,
chainstate_config: ChainstateConfig,
chainstate_storage: chainstate_storage::Store,
Expand Down Expand Up @@ -209,7 +210,7 @@ impl Chainstate {
}
}

pub fn attempt_to_process_block(
pub(crate) fn attempt_to_process_block(
&mut self,
block: Block,
block_source: BlockSource,
Expand Down Expand Up @@ -260,58 +261,59 @@ impl Chainstate {
}

/// returns the block index of the new tip
pub fn process_block(
pub(crate) fn process_block(
&mut self,
block: Block,
block_source: BlockSource,
) -> Result<Option<BlockIndex>, BlockError> {
self.attempt_to_process_block(block, block_source, 0)
}

pub fn preliminary_block_check(&self, block: Block) -> Result<Block, BlockError> {
pub(crate) fn preliminary_block_check(&self, block: Block) -> Result<Block, BlockError> {
let chainstate_ref = self.make_db_tx_ro();
chainstate_ref.check_block(&block)?;
Ok(block)
}

pub fn get_best_block_id(&self) -> Result<Option<Id<Block>>, PropertyQueryError> {
pub(crate) fn get_best_block_id(&self) -> Result<Option<Id<Block>>, PropertyQueryError> {
self.make_db_tx_ro().get_best_block_id()
}

pub fn get_header_from_height(
#[allow(dead_code)]
pub(crate) fn get_header_from_height(
&self,
height: &BlockHeight,
) -> Result<Option<BlockHeader>, PropertyQueryError> {
self.make_db_tx_ro().get_header_from_height(height)
}

pub fn get_block_id_from_height(
pub(crate) fn get_block_id_from_height(
&self,
height: &BlockHeight,
) -> Result<Option<Id<Block>>, PropertyQueryError> {
self.make_db_tx_ro().get_block_id_by_height(height)
}

pub fn get_block(&self, id: Id<Block>) -> Result<Option<Block>, PropertyQueryError> {
pub(crate) fn get_block(&self, id: Id<Block>) -> Result<Option<Block>, PropertyQueryError> {
self.make_db_tx_ro().get_block(id)
}

pub fn get_block_index(
pub(crate) fn get_block_index(
&self,
id: &Id<Block>,
) -> Result<Option<BlockIndex>, PropertyQueryError> {
self.make_db_tx_ro().get_block_index(id)
}

pub fn get_best_block_index(&self) -> Result<Option<BlockIndex>, PropertyQueryError> {
pub(crate) fn get_best_block_index(&self) -> Result<Option<BlockIndex>, PropertyQueryError> {
self.make_db_tx_ro().get_best_block_index()
}

fn locator_tip_distances() -> impl Iterator<Item = BlockDistance> {
itertools::iterate(0, |&i| std::cmp::max(1, i * 2)).map(BlockDistance::new)
}

pub fn get_locator(&self) -> Result<Locator, PropertyQueryError> {
pub(crate) fn get_locator(&self) -> Result<Locator, PropertyQueryError> {
let chainstate_ref = self.make_db_tx_ro();
let best_block_index = chainstate_ref
.get_best_block_index()?
Expand All @@ -326,14 +328,17 @@ impl Chainstate {
.map(Locator::new)
}

pub fn get_block_height_in_main_chain(
pub(crate) fn get_block_height_in_main_chain(
&self,
id: &Id<Block>,
) -> Result<Option<BlockHeight>, PropertyQueryError> {
self.make_db_tx_ro().get_block_height_in_main_chain(id)
}

pub fn get_headers(&self, locator: Locator) -> Result<Vec<BlockHeader>, PropertyQueryError> {
pub(crate) fn get_headers(
&self,
locator: Locator,
) -> Result<Vec<BlockHeader>, PropertyQueryError> {
// use genesis block if no common ancestor with better block height is found
let chainstate_ref = self.make_db_tx_ro();
let mut best = BlockHeight::new(0);
Expand Down Expand Up @@ -364,7 +369,7 @@ impl Chainstate {
itertools::process_results(headers, |iter| iter.flatten().collect::<Vec<_>>())
}

pub fn filter_already_existing_blocks(
pub(crate) fn filter_already_existing_blocks(
&self,
headers: Vec<BlockHeader>,
) -> Result<Vec<BlockHeader>, PropertyQueryError> {
Expand Down
80 changes: 66 additions & 14 deletions chainstate/src/detail/tests/double_spend_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use common::{
},
primitives::{time, Amount, Id},
};
use crypto::random::{self, Rng};

// Process a block where the second transaction uses the first one as input.
//
Expand All @@ -43,8 +42,9 @@ fn spend_output_in_the_same_block() {
common::concurrency::model(|| {
let mut chainstate = setup_chainstate();

let first_tx = tx_from_genesis(&chainstate);
let second_tx = tx_from_tx(&first_tx);
let mut rng = make_seedable_rng(None);
let first_tx = tx_from_genesis(&chainstate, rng.gen_range(100_000..200_000));
let second_tx = tx_from_tx(&first_tx, rng.gen_range(1000..2000));

let block = Block::new(
vec![first_tx, second_tx],
Expand Down Expand Up @@ -84,8 +84,9 @@ fn spend_output_in_the_same_block_invalid_order() {
common::concurrency::model(|| {
let mut chainstate = setup_chainstate();

let first_tx = tx_from_genesis(&chainstate);
let second_tx = tx_from_tx(&first_tx);
let mut rng = make_seedable_rng(None);
let first_tx = tx_from_genesis(&chainstate, rng.gen_range(100_000..200_000));
let second_tx = tx_from_tx(&first_tx, rng.gen_range(1000..2000));

let block = Block::new(
vec![second_tx, first_tx],
Expand Down Expand Up @@ -130,9 +131,10 @@ fn double_spend_tx_in_the_same_block() {
common::concurrency::model(|| {
let mut chainstate = setup_chainstate();

let first_tx = tx_from_genesis(&chainstate);
let second_tx = tx_from_tx(&first_tx);
let third_tx = tx_from_tx(&first_tx);
let mut rng = make_seedable_rng(None);
let first_tx = tx_from_genesis(&chainstate, rng.gen_range(100_000..200_000));
let second_tx = tx_from_tx(&first_tx, rng.gen_range(1000..2000));
let third_tx = tx_from_tx(&first_tx, rng.gen_range(1000..2000));

let block = Block::new(
vec![first_tx, second_tx, third_tx],
Expand Down Expand Up @@ -181,7 +183,8 @@ fn double_spend_tx_in_another_block() {
common::concurrency::model(|| {
let mut chainstate = setup_chainstate();

let first_tx = tx_from_genesis(&chainstate);
let mut rng = make_seedable_rng(None);
let first_tx = tx_from_genesis(&chainstate, rng.gen_range(100_000..200_000));
let first_block = Block::new(
vec![first_tx.clone()],
Some(Id::new(chainstate.chain_config.genesis_block_id().get())),
Expand All @@ -199,7 +202,7 @@ fn double_spend_tx_in_another_block() {
Some(first_block_id.clone())
);

let second_tx = tx_from_genesis(&chainstate);
let second_tx = tx_from_genesis(&chainstate, rng.gen_range(100_000..200_000));
let second_block = Block::new(
vec![second_tx],
Some(first_block_id.clone()),
Expand All @@ -224,8 +227,57 @@ fn double_spend_tx_in_another_block() {
});
}

// Try to process a block where the second transaction's input is more then first output.
//
// +--Block----------------+
// | |
// | +-------tx-1--------+ |
// | |input = prev_block | |
// | +-------------------+ |
// | |
// | +-------tx-2--------+ |
// | |input = tx1 | |
// | +-------------------+ |
// +-----------------------+
#[test]
fn spend_bigger_output_in_the_same_block() {
common::concurrency::model(move || {
let mut chainstate = setup_chainstate();

let mut rng = make_seedable_rng(None);
let tx1_output_value = rng.gen_range(1000..2000);
let tx2_output_value = rng.gen_range(100_000..200_000);
let first_tx = tx_from_genesis(&chainstate, tx1_output_value);
let second_tx = tx_from_tx(&first_tx, tx2_output_value);

let block = Block::new(
vec![first_tx, second_tx],
Some(Id::new(chainstate.chain_config.genesis_block_id().get())),
BlockTimestamp::from_duration_since_epoch(time::get()),
ConsensusData::None,
)
.expect(ERR_CREATE_BLOCK_FAIL);

assert_eq!(
chainstate.process_block(block, BlockSource::Local).unwrap_err(),
BlockError::StateUpdateFailed(StateUpdateError::AttemptToPrintMoney(
Amount::from_atoms(tx1_output_value),
Amount::from_atoms(tx2_output_value)
))
);
assert_eq!(
chainstate
.chainstate_storage
.get_best_block_id()
.expect(ERR_BEST_BLOCK_NOT_FOUND)
.expect(ERR_STORAGE_FAIL),
chainstate.chain_config.genesis_block_id()
);
});
}

// Creates a transaction with an input based on the first transaction from the genesis block.
fn tx_from_genesis(chainstate: &Chainstate) -> Transaction {
fn tx_from_genesis(chainstate: &Chainstate, output_value: u128) -> Transaction {
let genesis_block_tx_id =
chainstate.chain_config.genesis_block().transactions().get(0).unwrap().get_id();
let input = TxInput::new(
Expand All @@ -234,17 +286,17 @@ fn tx_from_genesis(chainstate: &Chainstate) -> Transaction {
empty_witness(),
);
let output = TxOutput::new(
Amount::from_atoms(random::make_pseudo_rng().gen_range(100_000..200_000)),
Amount::from_atoms(output_value),
OutputPurpose::Transfer(anyonecanspend_address()),
);
Transaction::new(0, vec![input], vec![output], 0).expect(ERR_CREATE_TX_FAIL)
}

// Creates a transaction with an input based on the specified transaction id.
fn tx_from_tx(tx: &Transaction) -> Transaction {
fn tx_from_tx(tx: &Transaction, output_value: u128) -> Transaction {
let input = TxInput::new(tx.get_id().into(), 0, InputWitness::NoSignature(None));
let output = TxOutput::new(
Amount::from_atoms(random::make_pseudo_rng().gen_range(1000..2000)),
Amount::from_atoms(output_value),
OutputPurpose::Transfer(anyonecanspend_address()),
);
Transaction::new(0, vec![input], vec![output], 0).expect(ERR_CREATE_TX_FAIL)
Expand Down
5 changes: 2 additions & 3 deletions chainstate/src/detail/tests/events_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use std::sync::Arc;

use crate::detail::tests::*;
use chainstate_storage::Store;
use crypto::random::{self, Rng};

type ErrorList = Arc<Mutex<Vec<BlockError>>>;

Expand Down Expand Up @@ -67,7 +66,7 @@ fn several_subscribers() {
common::concurrency::model(|| {
let mut chainstate = setup_chainstate();

let mut rng = random::make_pseudo_rng();
let mut rng = make_seedable_rng(None);
let subscribers = rng.gen_range(8..256);
let events = subscribe(&mut chainstate, subscribers);

Expand All @@ -90,7 +89,7 @@ fn several_subscribers_several_events() {
common::concurrency::model(|| {
let mut chainstate = setup_chainstate();

let mut rng = random::make_pseudo_rng();
let mut rng = make_seedable_rng(None);
let subscribers = rng.gen_range(4..16);
let blocks = rng.gen_range(8..128);

Expand Down
Loading