use exonum::{
blockchain::{BlockchainMut, CallInBlock, CallProof, ProposerId},
crypto::KeyPair,
helpers::{Height, ValidatorId},
merkledb::ObjectHash,
messages::{AnyTx, Verified},
runtime::ErrorKind as ExecutionErrorKind,
};
use serde_json::json;
use std::{collections::BTreeMap, iter};
use exonum_explorer::*;
#[path = "../tests/blockchain/mod.rs"]
mod blockchain;
use crate::blockchain::{
consensus_keys, create_block, create_blockchain, CreateWallet, ExplorerTransactions as _,
Transfer, SERVICE_ID,
};
pub fn mempool_transaction() -> Verified<AnyTx> {
consensus_keys().create_wallet(SERVICE_ID, CreateWallet::new("Alex"))
}
pub fn sample_blockchain() -> BlockchainMut {
let mut blockchain = create_blockchain();
let alice = KeyPair::random();
let bob = KeyPair::random();
let tx_alice = alice.create_wallet(SERVICE_ID, CreateWallet::new("Alice"));
let tx_bob = bob.create_wallet(SERVICE_ID, CreateWallet::new("Bob"));
let tx_transfer = alice.transfer(SERVICE_ID, Transfer::new(bob.public_key(), 100));
create_block(&mut blockchain, vec![tx_alice, tx_bob, tx_transfer]);
blockchain.add_transactions_into_pool(iter::once(mempool_transaction()));
blockchain
}
fn main() {
let blockchain = sample_blockchain();
let snapshot = blockchain.snapshot();
let explorer = BlockchainExplorer::new(snapshot.as_ref());
let block: BlockInfo<'_> = explorer.block(Height(1)).unwrap();
assert_eq!(block.height(), Height(1));
assert_eq!(block.len(), 3);
for tx in &block {
println!("{:?}: {:?}", tx.location(), tx.message());
}
assert_eq!(
serde_json::to_value(&block).unwrap(),
json!({
"block": block.header(),
"precommits": *block.precommits(),
"txs": *block.transaction_hashes(),
})
);
let block: BlockWithTransactions = explorer.block_with_txs(Height(1)).unwrap();
assert_eq!(block.height(), Height(1));
assert_eq!(block.len(), 3);
for tx in &block {
println!("{:?}: {:?}", tx.location(), tx.message());
}
let tx: &CommittedTransaction = &block[1];
assert_eq!(tx.location().position_in_block(), 1);
let tx = explorer.block(Height(1)).unwrap().transaction(0).unwrap();
assert_eq!(tx.location().block_height(), Height(1));
assert_eq!(tx.location().position_in_block(), 0);
let message = tx.message();
println!("{:?}", message);
let status = tx.status();
assert!(status.is_ok());
assert_eq!(
serde_json::to_value(&tx).unwrap(),
json!({
"message": serde_json::to_value(tx.message()).unwrap(),
"location": {
"block_height": 1,
"position_in_block": 0,
},
"location_proof": tx.location_proof(),
"status": { "type": "success" },
"time": tx.time(),
})
);
let block_info = explorer.block(Height(1)).unwrap();
let erroneous_tx = block_info.transaction(1).unwrap();
assert_eq!(
serde_json::to_value(&erroneous_tx).unwrap(),
json!({
"status": {
"type": "service_error",
"code": 0,
"description": "Not allowed!",
"runtime_id": 0,
"call_site": {
"call_type": "method",
"instance_id": SERVICE_ID,
"method_id": 0,
},
},
"message": serde_json::to_value(erroneous_tx.message()).unwrap(),
"location": erroneous_tx.location(),
"location_proof": erroneous_tx.location_proof(),
"time": erroneous_tx.time(),
})
);
for (i, error) in block.errors.iter().enumerate() {
println!("Error #{}: {}", i + 1, error);
}
let errors: BTreeMap<_, _> = block.error_map();
assert_eq!(errors.len(), 2);
assert_eq!(
errors[&CallInBlock::transaction(1)].description(),
"Not allowed!"
);
assert_eq!(
errors[&CallInBlock::transaction(2)].kind(),
ExecutionErrorKind::Unexpected
);
let proof: CallProof = block_info.call_proof(CallInBlock::transaction(1));
let validator_keys = [consensus_keys().public_key()];
let (_, res) = proof.verify(&validator_keys).unwrap();
assert_eq!(res.unwrap_err().description(), "Not allowed!");
let panicked_tx = explorer.block(Height(1)).unwrap().transaction(2).unwrap();
assert_eq!(
serde_json::to_value(&panicked_tx).unwrap(),
json!({
"status": {
"type": "unexpected_error",
"description": "oops",
"runtime_id": 0,
"call_site": {
"call_type": "method",
"instance_id": SERVICE_ID,
"method_id": 1,
},
},
"message": serde_json::to_value(panicked_tx.message()).unwrap(),
"location": panicked_tx.location(),
"location_proof": panicked_tx.location_proof(),
"time": panicked_tx.time(),
})
);
let hash = mempool_transaction().object_hash();
let tx: TransactionInfo = explorer.transaction(&hash).unwrap();
assert!(tx.is_in_pool());
println!("{:?}", tx.message());
let committed_tx: TransactionInfo = explorer
.transaction(&block[0].message().object_hash())
.unwrap();
let tx_ref = committed_tx.as_committed().unwrap();
assert_eq!(
serde_json::to_value(&committed_tx).unwrap(),
json!({
"type": "committed",
"message": serde_json::to_value(committed_tx.message()).unwrap(),
"status": { "type": "success" },
"location": tx_ref.location(),
"location_proof": tx_ref.location_proof(),
"time": tx_ref.time(),
})
);
let tx_in_pool: TransactionInfo = explorer
.transaction(&mempool_transaction().object_hash())
.unwrap();
assert_eq!(
serde_json::to_value(&tx_in_pool).unwrap(),
json!({
"type": "in_pool",
"message": serde_json::to_value(tx_in_pool.message()).unwrap(),
})
);
let tx_count: usize = explorer.blocks(..Height(10)).map(|block| block.len()).sum();
assert_eq!(tx_count, 3);
let block_count = explorer
.blocks(Height(1)..) .filter(|block| block.header().get_header::<ProposerId>().unwrap() == Some(ValidatorId(0)))
.count();
assert_eq!(block_count, 1);
}