Skip to content
This repository has been archived by the owner on Jul 27, 2022. It is now read-only.

Commit

Permalink
Problem (CRO-594): block results and "fast sync" are not verified in …
Browse files Browse the repository at this point in the history
…client

Solution:
- Store history hashes to `COL_APP_HASHS`, history state to `COL_APP_STATES`
- Add `query_state_batch` to rpc client, and verify block results with it.
- Make fast-forward syncing optional
- Improve BlockGenerator to pass manual synchronizer test
  • Loading branch information
yihuang committed Dec 5, 2019
1 parent 1a917cb commit c223624
Show file tree
Hide file tree
Showing 34 changed files with 674 additions and 197 deletions.
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.

57 changes: 36 additions & 21 deletions chain-abci/src/app/app_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::convert::TryInto;

use abci::*;
use enclave_protocol::{EnclaveRequest, EnclaveResponse};
use integer_encoding::VarInt;
use kvdb::DBTransaction;
use log::{info, warn};
use parity_scale_codec::{Decode, Encode};
Expand All @@ -28,7 +29,7 @@ use chain_core::init::config::NetworkParameters;
use chain_core::state::account::StakedStateDestination;
use chain_core::state::account::{CouncilNode, StakedState, StakedStateAddress};
use chain_core::state::tendermint::{BlockHeight, TendermintValidatorAddress, TendermintVotePower};
use chain_core::state::RewardsPoolState;
use chain_core::state::{ChainState, RewardsPoolState};
use chain_core::tx::TxAux;

/// Validator state tracking
Expand Down Expand Up @@ -87,27 +88,24 @@ pub struct ChainNodeState {
pub last_apphash: H256,
/// time in previous block's header or genesis time
pub block_time: Timespec,
/// root hash of the sparse merkle patricia trie of staking account states
pub last_account_root_hash: StarlingFixedKey,
/// last rewards pool state
pub rewards_pool: RewardsPoolState,
/// Record how many block each validator proposed, used for rewards distribution,
/// cleared after rewards distributed
pub proposer_stats: BTreeMap<StakedStateAddress, u64>,
/// network parameters (fee policy, staking configuration etc.)
pub network_params: NetworkParameters,
/// state of validators (keys, voting power, punishments, rewards...)
#[serde(skip)]
pub validators: ValidatorState,
/// genesis time
pub genesis_time: Timespec,

/// The parts of states which involved in computing app_hash
pub top_level: ChainState,
}

impl ChainNodeState {
pub fn genesis(
genesis_apphash: H256,
genesis_time: Timespec,
last_account_root_hash: StarlingFixedKey,
account_root: StarlingFixedKey,
rewards_pool: RewardsPoolState,
network_params: NetworkParameters,
validators: ValidatorState,
Expand All @@ -116,12 +114,14 @@ impl ChainNodeState {
last_block_height: 0,
last_apphash: genesis_apphash,
block_time: genesis_time,
last_account_root_hash,
rewards_pool,
proposer_stats: BTreeMap::new(),
network_params,
validators,
genesis_time,
top_level: ChainState {
account_root,
rewards_pool,
network_params,
},
}
}
}
Expand Down Expand Up @@ -174,10 +174,10 @@ fn get_validator_mapping(
.council_nodes_by_power
.iter()
.rev()
.take(last_app_state.network_params.get_max_validators())
.take(last_app_state.top_level.network_params.get_max_validators())
{
// integrity checks -- committed / disk-persisted values should match up
let account = get_account(&address, &last_app_state.last_account_root_hash, accounts)
let account = get_account(&address, &last_app_state.top_level.account_root, accounts)
.expect("council node staking state address should be in the state trie");
assert!(
&account.council_node.is_some(),
Expand All @@ -186,6 +186,7 @@ fn get_validator_mapping(
if account.is_jailed()
|| account.bonded
< last_app_state
.top_level
.network_params
.get_required_council_node_stake()
{
Expand Down Expand Up @@ -248,13 +249,27 @@ fn get_voting_power(
}
}

fn store_valid_genesis_state(genesis_state: &ChainNodeState, inittx: &mut DBTransaction) {
let encoded = genesis_state.encode();
inittx.put(COL_NODE_INFO, LAST_STATE_KEY, &encoded);
inittx.put(COL_EXTRA, b"init_chain_state", &encoded);
fn store_valid_genesis_state(
genesis_state: &ChainNodeState,
inittx: &mut DBTransaction,
write_history_states: bool,
) {
inittx.put(COL_NODE_INFO, LAST_STATE_KEY, &genesis_state.encode());
let encoded_height = i64::encode_var_vec(0);
inittx.put(COL_APP_HASHS, &encoded_height, &genesis_state.last_apphash);
if write_history_states {
inittx.put(
COL_APP_STATES,
&encoded_height,
&genesis_state.top_level.encode(),
);
}
}

fn compute_accounts_root(account_storage: &mut AccountStorage, accounts: &[StakedState]) -> H256 {
pub fn compute_accounts_root(
account_storage: &mut AccountStorage,
accounts: &[StakedState],
) -> H256 {
let mut keys: Vec<_> = accounts.iter().map(StakedState::key).collect();
let wrapped: Vec<_> = accounts.iter().cloned().map(AccountWrapper).collect();
account_storage
Expand Down Expand Up @@ -319,7 +334,7 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
storage,
accounts,
delivered_txs: Vec::new(),
uncommitted_account_root_hash: last_app_state.last_account_root_hash,
uncommitted_account_root_hash: last_app_state.top_level.account_root,
chain_hex_id,
genesis_app_hash,
last_state: Some(last_app_state),
Expand Down Expand Up @@ -575,13 +590,13 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
},
},
);
store_valid_genesis_state(&genesis_state, &mut inittx);
store_valid_genesis_state(&genesis_state, &mut inittx, self.tx_query_address.is_some());

let wr = self.storage.db.write(inittx);
if wr.is_err() {
panic!("db write error: {}", wr.err().unwrap());
} else {
self.uncommitted_account_root_hash = genesis_state.last_account_root_hash;
self.uncommitted_account_root_hash = genesis_state.top_level.account_root;
self.last_state = Some(genesis_state);
}

Expand Down
29 changes: 17 additions & 12 deletions chain-abci/src/app/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
pub fn commit_handler(&mut self, _req: &RequestCommit) -> ResponseCommit {
let orig_state = self.last_state.clone();
let mut new_state = orig_state.expect("executing block commit, but no app state stored (i.e. no initchain or recovery was executed)");
let mut top_level = &mut new_state.top_level;
let mut resp = ResponseCommit::new();
let mut inittx = self.storage.db.transaction();

Expand All @@ -136,15 +137,15 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
self.process_txs(&mut inittx);
}
if self.rewards_pool_updated {
new_state.rewards_pool.last_block_height = new_state.last_block_height;
top_level.rewards_pool.last_block_height = new_state.last_block_height;
self.rewards_pool_updated = false;
}
new_state.last_account_root_hash = self.uncommitted_account_root_hash;
top_level.account_root = self.uncommitted_account_root_hash;
let app_hash = compute_app_hash(
&tree,
&new_state.last_account_root_hash,
&new_state.rewards_pool,
&new_state.network_params,
&top_level.account_root,
&top_level.rewards_pool,
&top_level.network_params,
);
inittx.put(COL_MERKLE_PROOFS, &app_hash[..], &tree.encode());
new_state.last_apphash = app_hash;
Expand All @@ -154,10 +155,10 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
app_hash,
info: ChainInfo {
// TODO: fee computation in enclave?
min_fee_computed: new_state.network_params.calculate_fee(0).expect("base fee"),
min_fee_computed: top_level.network_params.calculate_fee(0).expect("base fee"),
chain_hex_id: self.chain_hex_id,
previous_block_time: new_state.block_time,
unbonding_period: new_state.network_params.get_unbonding_period(),
unbonding_period: top_level.network_params.get_unbonding_period(),
},
}) {
EnclaveResponse::CommitBlock(Ok(_)) => {
Expand All @@ -168,12 +169,16 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
}
}

inittx.put(
COL_APP_STATES,
&i64::encode_var_vec(new_state.last_block_height),
&new_state.last_apphash,
);
inittx.put(COL_NODE_INFO, LAST_STATE_KEY, &new_state.encode());
let encoded_height = i64::encode_var_vec(new_state.last_block_height);
inittx.put(COL_APP_HASHS, &encoded_height, &new_state.last_apphash);
if self.tx_query_address.is_some() {
inittx.put(
COL_APP_STATES,
&encoded_height,
&new_state.top_level.encode(),
);
}
let wr = self.storage.db.write(inittx);
if wr.is_err() {
panic!("db write error: {}", wr.err().unwrap());
Expand Down
7 changes: 5 additions & 2 deletions chain-abci/src/app/end_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn get_validator_updates(
.council_nodes_by_power
.iter()
.rev()
.take(last_state.network_params.get_max_validators())
.take(last_state.top_level.network_params.get_max_validators())
{
let old_power = validator_voting_power.get(&address);
let create_update = match old_power {
Expand Down Expand Up @@ -138,7 +138,10 @@ fn get_validator_updates(
}
}
}
let window = last_state.network_params.get_block_signing_window();
let window = last_state
.top_level
.network_params
.get_block_signing_window();
while let Some((validator_address, staking_address)) = new_to_track.pop() {
last_state.validators.add_validator_for_tracking(
validator_address,
Expand Down
2 changes: 1 addition & 1 deletion chain-abci/src/app/jail_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
.expect("Last state not found. Init chain was not called.");

let block_time = last_state.block_time;
let jail_duration = last_state.network_params.get_jail_duration();
let jail_duration = last_state.top_level.network_params.get_jail_duration();

account.jail_until(block_time + jail_duration, punishment_kind);

Expand Down
19 changes: 14 additions & 5 deletions chain-abci/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use abci::*;
use log::info;

pub use self::app_init::{
get_validator_key, init_app_hash, ChainNodeApp, ChainNodeState, ValidatorState,
compute_accounts_root, get_validator_key, init_app_hash, ChainNodeApp, ChainNodeState,
ValidatorState,
};
use crate::enclave_bridge::EnclaveProxy;
use crate::slashing::SlashingSchedule;
Expand Down Expand Up @@ -167,13 +168,19 @@ impl<T: EnclaveProxy> abci::Application for ChainNodeApp<T> {

accounts_to_punish.push((
account_address,
last_state.network_params.get_byzantine_slash_percent(),
last_state
.top_level
.network_params
.get_byzantine_slash_percent(),
PunishmentKind::ByzantineFault,
))
}
}

let missed_block_threshold = last_state.network_params.get_missed_block_threshold();
let missed_block_threshold = last_state
.top_level
.network_params
.get_missed_block_threshold();

accounts_to_punish.extend(
last_state
Expand All @@ -188,14 +195,14 @@ impl<T: EnclaveProxy> abci::Application for ChainNodeApp<T> {
(
*last_state.validators.tendermint_validator_addresses.get(tendermint_validator_address)
.expect("Staking account address for tendermint validator address not found"),
last_state.network_params.get_liveness_slash_percent(),
last_state.top_level.network_params.get_liveness_slash_percent(),
PunishmentKind::NonLive,
)
}),
);

let slashing_time =
last_state.block_time + last_state.network_params.get_slash_wait_period();
last_state.block_time + last_state.top_level.network_params.get_slash_wait_period();
let slashing_proportion =
self.get_slashing_proportion(accounts_to_punish.iter().map(|x| x.0));

Expand Down Expand Up @@ -370,6 +377,7 @@ impl<T: EnclaveProxy> abci::Application for ChainNodeApp<T> {
self.last_state
.as_ref()
.expect("delivertx should have app state")
.top_level
.network_params
.get_required_council_node_stake(),
);
Expand Down Expand Up @@ -409,6 +417,7 @@ impl<T: EnclaveProxy> abci::Application for ChainNodeApp<T> {
.last_state
.as_mut()
.expect("deliver tx, but last state not initialized")
.top_level
.rewards_pool;
let new_remaining = (rewards_pool.period_bonus + fee_acc.0.to_coin())
.expect("rewards pool + fee greater than max coin?");
Expand Down
34 changes: 31 additions & 3 deletions chain-abci/src/app/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use crate::storage::*;
use abci::*;
use chain_core::common::{MerkleTree, Proof as MerkleProof, H256, HASH_SIZE_256};
use chain_core::state::account::StakedStateAddress;
use chain_core::state::ChainState;
use chain_core::tx::data::{txid_hash, TXID_HASH_ID};
use integer_encoding::VarInt;
use parity_scale_codec::{Decode, Encode};
use serde_json;
use std::convert::TryFrom;

/// Generate generic ABCI ProofOp for the witness
Expand Down Expand Up @@ -82,13 +84,13 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
let app_hash = self
.storage
.db
.get(COL_APP_STATES, &i64::encode_var_vec(height))
.get(COL_APP_HASHS, &i64::encode_var_vec(height))
.unwrap()
.unwrap();
let data = self
.storage
.db
.get(COL_MERKLE_PROOFS, &app_hash[..])
.get(COL_MERKLE_PROOFS, &app_hash)
.unwrap()
.unwrap()
.to_vec();
Expand Down Expand Up @@ -136,7 +138,7 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
let account_address = StakedStateAddress::try_from(_req.data.as_slice());
if let (Some(state), Ok(address)) = (&self.last_state, account_address) {
let account =
get_account(&address, &state.last_account_root_hash, &self.accounts);
get_account(&address, &state.top_level.account_root, &self.accounts);
match account {
Ok(a) => {
resp.value = a.encode();
Expand All @@ -152,6 +154,32 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
resp.code = 3;
}
}
"state" => {
if self.tx_query_address.is_none() {
resp.code = 1;
resp.log += "tx query address not set";
} else {
let value = self
.storage
.db
.get(COL_APP_STATES, &i64::encode_var_vec(_req.height));
match value {
Ok(Some(value)) => {
if let Ok(state) = ChainState::decode(&mut value.to_vec().as_slice()) {
resp.value =
serde_json::to_string(&state).unwrap().as_bytes().to_owned();
} else {
resp.log += "state decode failed";
resp.code = 2;
}
}
_ => {
resp.log += "state not found";
resp.code = 2;
}
}
}
}
_ => {
resp.log += "invalid path";
resp.code = 1;
Expand Down
Loading

0 comments on commit c223624

Please sign in to comment.