Skip to content

Commit

Permalink
Problem (Fix crypto-com#1313): light client doesn't verify the fetche…
Browse files Browse the repository at this point in the history
…d staking state

Solution:
- Support query merkle inclusion proof
- Also support query historical staking state
- Query json encoded staking from abci_query directly crypto-com#1464
  • Loading branch information
yihuang committed Apr 22, 2020
1 parent 2cb1f71 commit 470d7ab
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
*Unreleased*
## v0.5.0
### Breaking changes

- *chain-storage* [1466](https://github.com/crypto-com/chain/pull/1466): add a colume to store historical staking versions

### Features
### Improvements
### Bug Fixes
Expand Down
4 changes: 4 additions & 0 deletions chain-abci/src/app/app_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ impl StoredChainState for ChainNodeState {
fn get_last_app_hash(&self) -> H256 {
self.last_apphash
}

fn get_staking_version(&self) -> Version {
self.staking_version
}
}

impl ChainNodeState {
Expand Down
21 changes: 21 additions & 0 deletions chain-abci/src/app/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ impl<T: EnclaveProxy> ChainNodeApp<T> {
resp.code = 3;
}
}
"staking" => {
let height: BlockHeight = _req.height.try_into().expect("Invalid block height");
let mversion = if height == BlockHeight::genesis() {
self.last_state.as_ref().map(|state| state.staking_version)
} else {
self.storage.get_historical_staking_version(height)
};
let account_address = StakedStateAddress::try_from(_req.data.as_slice());
if let (Some(version), Ok(address)) = (mversion, account_address) {
let (maccount, proof) = get_with_proof(&self.storage, version, &address);
resp.value = serde_json::to_string(&(
maccount,
if _req.prove { Some(proof) } else { None },
))
.unwrap()
.into_bytes();
} else {
resp.log += "account lookup failed (either invalid address or node not correctly restored / initialized)";
resp.code = 3;
}
}
"state" => {
if self.tx_query_address.is_none() {
resp.code = 1;
Expand Down
17 changes: 15 additions & 2 deletions chain-abci/tests/abci_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,8 +760,21 @@ fn query_should_return_an_account() {
qreq.data = hex::decode(&addr).unwrap();
qreq.path = "account".into();
let qresp = app.query(&qreq);
let account = StakedState::decode(&mut qresp.value.as_slice());
assert!(account.is_ok());
let account = StakedState::decode(&mut qresp.value.as_slice()).unwrap();
assert_eq!(account.address, StakedStateAddress::from_str(addr).unwrap());
}

#[test]
fn staking_query_should_return_an_account() {
let addr = "fe7c045110b8dbf29765047380898919c5cb56f9";
let mut app = init_chain_for(addr.parse().unwrap());
let mut qreq = RequestQuery::new();
qreq.data = hex::decode(&addr).unwrap();
qreq.path = "staking".into();
let qresp = app.query(&qreq);
let (account, _): (StakedState, serde_json::Value) =
serde_json::from_slice(&qresp.value).unwrap();
assert_eq!(account.address, StakedStateAddress::from_str(addr).unwrap());
}

fn block_commit(app: &mut ChainNodeApp<MockClient>, tx: TxAux, block_height: i64) {
Expand Down
14 changes: 12 additions & 2 deletions chain-storage/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::collections::BTreeMap;

use bit_vec::BitVec;
use parity_scale_codec::Encode;
use parity_scale_codec::{Decode, Encode};

use crate::jellyfish::Version;
use chain_core::common::H256;
use chain_core::state::tendermint::BlockHeight;
use chain_core::tx::data::{
Expand All @@ -13,7 +14,7 @@ use chain_core::tx::data::{
use super::buffer::{GetKV, StoreKV};
use super::{
LookupItem, StoredChainState, CHAIN_ID_KEY, COL_APP_HASHS, COL_APP_STATES, COL_EXTRA,
COL_NODE_INFO, GENESIS_APP_HASH_KEY, LAST_STATE_KEY,
COL_NODE_INFO, COL_STAKING_VERSIONS, GENESIS_APP_HASH_KEY, LAST_STATE_KEY,
};

pub fn get_last_app_state(db: &impl GetKV) -> Option<Vec<u8>> {
Expand Down Expand Up @@ -65,6 +66,11 @@ pub fn get_historical_app_hash(db: &impl GetKV, height: BlockHeight) -> Option<H
Some(stored_ah)
}

pub fn get_historical_staking_version(db: &impl GetKV, height: BlockHeight) -> Option<Version> {
let sah = db.get(&(COL_STAKING_VERSIONS, height.encode()))?;
Version::decode(&mut sah.as_slice()).ok()
}

pub fn store_chain_state<T: StoredChainState>(
db: &mut impl StoreKV,
genesis_state: &T,
Expand All @@ -80,6 +86,10 @@ pub fn store_chain_state<T: StoredChainState>(
(COL_APP_HASHS, encoded_height.clone()),
genesis_state.get_last_app_hash().to_vec(),
);
db.set(
(COL_STAKING_VERSIONS, encoded_height.clone()),
genesis_state.get_staking_version().encode(),
);
if write_history_states {
db.set(
(COL_APP_STATES, encoded_height),
Expand Down
12 changes: 10 additions & 2 deletions chain-storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@ pub const COL_NODE_INFO: u32 = 4;
pub const COL_MERKLE_PROOFS: u32 = 5;
/// Column for tracking app hashes: height => app hash
pub const COL_APP_HASHS: u32 = 6;
/// Column for tracking app states: height => ChainNodeState, only available when tx_query_address set
/// Column for tracking app states: height => ChainState, only available when tx_query_address set
pub const COL_APP_STATES: u32 = 7;
/// Column for sealed transction payload: TxId => sealed tx payload (to MRSIGNER on a particular machine)
pub const COL_ENCLAVE_TX: u32 = 8;
/// Column for merkle trie storage
pub const COL_TRIE_NODE: u32 = 9;
/// Column for staled node key in merkle trie
pub const COL_TRIE_STALED: u32 = 10;
/// Column to store block height -> staking version
pub const COL_STAKING_VERSIONS: u32 = 11;
/// Number of columns in DB
pub const NUM_COLUMNS: u32 = 11;
pub const NUM_COLUMNS: u32 = 12;

pub const CHAIN_ID_KEY: &[u8] = b"chain_id";
pub const GENESIS_APP_HASH_KEY: &[u8] = b"genesis_app_hash";
Expand Down Expand Up @@ -134,6 +136,8 @@ pub trait StoredChainState {
fn get_encoded_top_level(&self) -> Vec<u8>;
/// the last committed application hash
fn get_last_app_hash(&self) -> H256;
/// the staking version
fn get_staking_version(&self) -> Version;
}

#[repr(u32)]
Expand Down Expand Up @@ -219,6 +223,10 @@ impl Storage {
get_historical_state(self, height)
}

pub fn get_historical_staking_version(&self, height: BlockHeight) -> Option<Version> {
get_historical_staking_version(self, height)
}

pub fn get_historical_app_hash(&self, height: BlockHeight) -> Option<H256> {
get_historical_app_hash(self, height)
}
Expand Down
37 changes: 33 additions & 4 deletions integration-tests/bot/chainrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import base64
import binascii
import json

import fire
from jsonrpcclient import request
Expand Down Expand Up @@ -48,6 +49,16 @@ def fix_address(addr):
return addr


def fix_address_hex(addr):
'fire convert staking addr to int automatically, fix it.'
if addr.startswith('0x'):
return addr[2:]
if isinstance(addr, int):
return '%040x' % addr
else:
return addr


class BaseService:
def __init__(self, base_port=None):
self.base_port = base_port if base_port is not None else config('BASE_PORT', 26650, cast=int)
Expand Down Expand Up @@ -279,7 +290,7 @@ def unconfirmed_txs(self):
def latest_height(self):
return self.status()['sync_info']['latest_block_height']

def validators(self, height=None, page = 0, num_per_page = 100):
def validators(self, height=None, page=0, num_per_page=100):
return self.call_chain('validators', str(height) if height is not None else None, str(page), str(num_per_page))

def block(self, height='latest'):
Expand All @@ -299,7 +310,10 @@ def commit(self, height='latest'):
return self.call_chain('commit', str(height))

def query(self, path, data=None, height=None, proof=False):
return self.call_chain('abci_query', path, fix_address(data), str(height) if height is not None else None, proof)
return self.call_chain(
'abci_query', path, fix_address_hex(data),
str(height) if height is not None else None, proof
)

def broadcast_tx_commit(self, tx):
return self.call_chain('broadcast_tx_commit', tx)
Expand All @@ -314,8 +328,23 @@ def tx(self, txid, include_proof=False):
txid = base64.b64encode(binascii.unhexlify(txid)).decode()
return self.call_chain('tx', txid, include_proof)

def tx_search(self, query, include_proof=False, page=1, per_page=100, order_by="asc"):
return self.call_chain('tx_search', query=query, prove=include_proof, page=str(page), per_page=str(per_page), order_by = order_by)
def tx_search(self, query, include_proof=False,
page=1, per_page=100, order_by="asc"):
return self.call_chain(
'tx_search', query=query, prove=include_proof,
page=str(page), per_page=str(per_page),
order_by=order_by
)

def staking(self, address, height=None, prove=False):
rsp = self.query("staking", address, height, prove)
rsp = rsp['response']
assert rsp['code'] == 0, rsp
state, proof = json.loads(base64.b64decode(rsp['value']))
if prove:
return state, proof
else:
return state


class RPC:
Expand Down

0 comments on commit 470d7ab

Please sign in to comment.