use crate::{
error::{Error, Result},
request::MethodRequest,
response::{MethodResponse, Response},
state::State,
};
use diem_json_rpc_types::views::{
AccountStateWithProofView, AccountView, CurrencyInfoView, EventView, MetadataView,
TransactionListView,
};
use diem_types::{
account_address::AccountAddress,
account_config::{diem_root_address, NewBlockEvent},
account_state::AccountState,
account_state_blob::AccountStateWithProof,
block_metadata::new_block_event_key,
contract_event::{default_protocol::{EventByVersionWithProof, EventWithProof}},
event::EventKey,
ledger_info::LedgerInfo,
proof::{AccumulatorConsistencyProof, TransactionAccumulatorSummary},
state_proof::StateProof,
transaction::{AccountTransactionsWithProof, Version},
trusted_state::TrustedState,
};
use std::convert::TryFrom;
pub(crate) struct VerifyingBatch {
requests: Vec<VerifyingRequest>,
}
impl VerifyingBatch {
pub(crate) fn from_batch(requests: Vec<MethodRequest>) -> Self {
Self {
requests: requests.into_iter().map(VerifyingRequest::from).collect(),
}
}
pub(crate) fn num_requests(&self, request_trusted_state: &TrustedState) -> usize {
let num_client_requests = self
.requests
.iter()
.map(|request| request.subrequests.len())
.sum::<usize>();
num_client_requests
+ 1
+ request_trusted_state.need_accumulator() as usize
}
pub(crate) fn collect_requests(
&self,
request_trusted_state: &TrustedState,
) -> Vec<MethodRequest> {
let request_version = request_trusted_state.version();
let mut requests = self
.requests
.iter()
.flat_map(|request| request.subrequests.iter().cloned())
.collect::<Vec<_>>();
if request_trusted_state.need_accumulator() {
requests.push(MethodRequest::get_accumulator_consistency_proof(
None,
Some(request_version),
));
}
requests.push(MethodRequest::get_state_proof(request_version));
requests
}
pub(crate) fn verify_responses(
self,
request_trusted_state: &TrustedState,
mut responses: Vec<Result<Response<MethodResponse>>>,
) -> Result<(
Option<TrustedState>,
Option<Vec<Result<Response<MethodResponse>>>>,
)> {
let request_version = request_trusted_state.version();
let num_requests = self.num_requests(request_trusted_state);
if num_requests != responses.len() {
return Err(Error::rpc_response(format!(
"expected {} responses, received {} responses in batch",
num_requests,
responses.len()
)));
}
let (state_proof_response, state) = responses.pop().unwrap()?.into_parts();
let state_proof_view = state_proof_response.try_into_get_state_proof()?;
let state_proof = StateProof::try_from(&state_proof_view).map_err(Error::decode)?;
verify_latest_li_matches_state(state_proof.latest_ledger_info(), &state)?;
for response in responses.iter().flatten() {
if response.state() != &state {
return Err(Error::rpc_response(format!(
"expected all responses in batch to have the same metadata: {:?}, \
received unexpected response metadata: {:?}",
state,
response.state(),
)));
}
}
let maybe_accumulator = if request_trusted_state.need_accumulator() {
let response = responses.pop().unwrap()?.into_inner();
let consistency_proof_view = response.try_into_get_accumulator_consistency_proof()?;
let consistency_proof = AccumulatorConsistencyProof::try_from(&consistency_proof_view)
.map_err(Error::decode)?;
let accumulator_summary = TransactionAccumulatorSummary::try_from_genesis_proof(
consistency_proof,
request_version,
)
.map_err(Error::invalid_proof)?;
Some(accumulator_summary)
} else {
None
};
let new_state = request_trusted_state
.verify_and_ratchet(&state_proof, maybe_accumulator.as_ref())
.map_err(Error::invalid_proof)?
.new_state();
if state_proof.epoch_changes().more {
return Ok((new_state, None));
}
let mut responses_iter = responses.into_iter();
let validated_responses = self
.requests
.into_iter()
.map(|request| {
let n = request.subrequests.len();
let subresponses = responses_iter.by_ref().take(n).collect();
request.validate_subresponses(&state, &state_proof, subresponses)
})
.collect::<Vec<_>>();
Ok((new_state, Some(validated_responses)))
}
}
fn verify_latest_li_matches_state(latest_li: &LedgerInfo, state: &State) -> Result<()> {
if latest_li.version() != state.version {
return Err(Error::invalid_proof(format!(
"latest LedgerInfo version ({}) doesn't match response version ({})",
latest_li.version(),
state.version,
)));
}
if latest_li.timestamp_usecs() != state.timestamp_usecs {
return Err(Error::invalid_proof(format!(
"latest LedgerInfo timestamp ({}) doesn't match response timestamp ({})",
latest_li.timestamp_usecs(),
state.timestamp_usecs,
)));
}
Ok(())
}
#[derive(Clone, Copy)]
struct RequestContext<'a> {
state: &'a State,
state_proof: &'a StateProof,
}
type RequestCallback =
Box<dyn FnOnce(RequestContext<'_>, &[MethodResponse]) -> Result<MethodResponse>>;
struct VerifyingRequest {
request: MethodRequest,
subrequests: Vec<MethodRequest>,
callback: RequestCallback,
}
impl VerifyingRequest {
fn new(
request: MethodRequest,
subrequests: Vec<MethodRequest>,
callback: RequestCallback,
) -> Self {
Self {
request,
subrequests,
callback,
}
}
fn map<F>(self, f: F) -> VerifyingRequest
where
F: FnOnce(RequestContext<'_>, MethodResponse) -> MethodResponse + 'static,
{
let inner = self.callback;
let callback: RequestCallback = Box::new(move |ctxt, subresponses| {
let response = inner(ctxt, subresponses)?;
Ok(f(ctxt, response))
});
Self::new(self.request, self.subrequests, callback)
}
fn validate_subresponses(
self,
state: &State,
state_proof: &StateProof,
subresponses: Vec<Result<Response<MethodResponse>>>,
) -> Result<Response<MethodResponse>> {
if subresponses.len() != self.subrequests.len() {
return Err(Error::rpc_response(format!(
"expected {} subresponses for our request {:?}, received {} subresponses in batch",
self.subrequests.len(),
self.request.method(),
subresponses.len(),
)));
}
let ctxt = RequestContext { state, state_proof };
let subresponses_only = subresponses
.into_iter()
.map(|result| result.map(Response::into_inner))
.collect::<Result<Vec<_>>>()?;
let response = (self.callback)(ctxt, subresponses_only.as_slice())?;
Ok(Response::new(response, state.clone()))
}
}
impl From<MethodRequest> for VerifyingRequest {
fn from(request: MethodRequest) -> Self {
match request {
MethodRequest::Submit((txn,)) => submit(txn),
MethodRequest::GetMetadata((None,)) => get_latest_metadata(),
MethodRequest::GetMetadata((Some(version),)) => get_historical_metadata(version),
MethodRequest::GetAccount(address, version) => get_account(address, version),
MethodRequest::GetTransactions(start_version, limit, include_events) => {
get_transactions(start_version, limit, include_events)
}
MethodRequest::GetAccountTransactions(
address,
start_seq_num,
limit,
include_events,
) => get_account_transactions(address, start_seq_num, limit, include_events),
MethodRequest::GetAccountTransaction(address, seq_num, include_events) => {
get_account_transaction(address, seq_num, include_events)
}
MethodRequest::GetEvents(key, start_seq, limit) => get_events(key, start_seq, limit),
MethodRequest::GetCurrencies([]) => get_currencies(),
MethodRequest::GetNetworkStatus([]) => get_network_status(),
_ => panic!(
"unsupported verifying client method: {:?}",
request.method()
),
}
}
}
fn submit(txn: String) -> VerifyingRequest {
let request = MethodRequest::Submit((txn,));
let subrequests = vec![request.clone()];
let callback: RequestCallback = Box::new(move |_ctxt, subresponses| {
match subresponses {
[MethodResponse::Submit] => (),
subresponses => {
return Err(Error::rpc_response(format!(
"expected [Submit] subresponses, received: {:?}",
subresponses,
)))
}
};
Ok(MethodResponse::Submit)
});
VerifyingRequest::new(request, subrequests, callback)
}
fn get_latest_metadata() -> VerifyingRequest {
let request = MethodRequest::GetMetadata((None,));
let subrequests = vec![MethodRequest::GetAccountStateWithProof(
diem_root_address(),
None,
None,
)];
let callback: RequestCallback = Box::new(move |ctxt, subresponses| {
let diem_root = match subresponses {
[MethodResponse::GetAccountStateWithProof(ref account)] => account,
subresponses => {
return Err(Error::rpc_response(format!(
"expected [GetAccountStateWithProof] subresponses, received: {:?}",
subresponses,
)))
}
};
let latest_li = ctxt.state_proof.latest_ledger_info();
let diem_root = verify_account_state(ctxt, diem_root, diem_root_address(), None)?
.ok_or_else(|| Error::rpc_response("DiemRoot account is missing"))?;
let version = latest_li.version();
let accumulator_root_hash = latest_li.transaction_accumulator_hash();
let timestamp = latest_li.timestamp_usecs();
let chain_id = ctxt.state.chain_id;
let mut metadata_view =
MetadataView::new(version, accumulator_root_hash, timestamp, chain_id);
metadata_view
.with_diem_root(&diem_root)
.map_err(Error::rpc_response)?;
Ok(MethodResponse::GetMetadata(metadata_view))
});
VerifyingRequest::new(request, subrequests, callback)
}
fn get_historical_metadata(version: Version) -> VerifyingRequest {
let request = MethodRequest::GetMetadata((Some(version),));
let subrequests = vec![
MethodRequest::GetAccumulatorConsistencyProof(None, Some(version)),
MethodRequest::GetAccumulatorConsistencyProof(Some(version), None),
MethodRequest::GetEventByVersionWithProof(new_block_event_key(), Some(version)),
];
let callback: RequestCallback = Box::new(move |ctxt, subresponses| {
let (consistency_pg2v, consistency_v2li, block_event) = match subresponses {
[MethodResponse::GetAccumulatorConsistencyProof(ref c1), MethodResponse::GetAccumulatorConsistencyProof(ref c2), MethodResponse::GetEventByVersionWithProof(ref e)] => (c1, c2, e),
subresponses => {
return Err(Error::rpc_response(format!(
"expected [GetAccumulatorConsistencyProof, GetAccumulatorConsistencyProof, GetEventByVersionWithProof] subresponses, received: {:?}",
subresponses,
)))
}
};
let latest_li = ctxt.state_proof.latest_ledger_info();
let consistency_pg2v =
AccumulatorConsistencyProof::try_from(consistency_pg2v).map_err(Error::decode)?;
let consistency_v2li =
AccumulatorConsistencyProof::try_from(consistency_v2li).map_err(Error::decode)?;
let block_event = EventByVersionWithProof::try_from(block_event).map_err(Error::decode)?;
let accumulator_summary =
TransactionAccumulatorSummary::try_from_genesis_proof(consistency_pg2v, version)
.map_err(Error::invalid_proof)?;
let accumulator_root_hash = accumulator_summary.root_hash();
let _ = accumulator_summary
.try_extend_with_proof(&consistency_v2li, latest_li)
.map_err(Error::invalid_proof)?;
let event_count = None;
block_event
.verify(latest_li, &new_block_event_key(), event_count, version)
.map_err(Error::invalid_proof)?;
let timestamp = match (block_event.lower_bound_incl, block_event.upper_bound_excl) {
(None, None) | (None, Some(_)) => {
if version == 0 {
0 } else {
return Err(Error::rpc_response("not genesis"));
}
}
(Some(block_event), Some(_)) => {
let block_event =
NewBlockEvent::try_from(&block_event.event).map_err(Error::decode)?;
block_event.proposed_time()
}
(Some(block_event), None) => {
let block_event =
NewBlockEvent::try_from(&block_event.event).map_err(Error::decode)?;
let timestamp = block_event.proposed_time();
if block_event.round() != latest_li.round()
|| timestamp != latest_li.timestamp_usecs()
{
return Err(Error::rpc_response("not latest block"));
}
timestamp
}
};
let chain_id = ctxt.state.chain_id;
let metadata_view = MetadataView::new(version, accumulator_root_hash, timestamp, chain_id);
Ok(MethodResponse::GetMetadata(metadata_view))
});
VerifyingRequest::new(request, subrequests, callback)
}
fn get_account(address: AccountAddress, version: Option<Version>) -> VerifyingRequest {
let request = MethodRequest::GetAccount(address, version);
let subrequests = vec![MethodRequest::GetAccountStateWithProof(
address, version, None,
)];
let callback: RequestCallback = Box::new(move |ctxt, subresponses| {
let account = match subresponses {
[MethodResponse::GetAccountStateWithProof(ref account)] => account,
subresponses => {
return Err(Error::rpc_response(format!(
"expected [GetAccountStateWithProof] subresponses, received: {:?}",
subresponses,
)))
}
};
let ledger_version = ctxt.state_proof.latest_ledger_info().version();
let version = version.unwrap_or(ledger_version);
let maybe_account_state = verify_account_state(ctxt, account, address, Some(version))?;
let maybe_account_view = maybe_account_state
.map(|account_state| {
AccountView::try_from_account_state(address, account_state, version)
.map_err(Error::decode)
})
.transpose()?;
Ok(MethodResponse::GetAccount(maybe_account_view))
});
VerifyingRequest::new(request, subrequests, callback)
}
fn get_transactions(start_version: Version, limit: u64, include_events: bool) -> VerifyingRequest {
let request = MethodRequest::GetTransactions(start_version, limit, include_events);
let subrequests = vec![MethodRequest::GetTransactionsWithProofs(
start_version,
limit,
include_events,
)];
let callback: RequestCallback = Box::new(move |ctxt, subresponses| {
let maybe_txs_with_proofs_view = match subresponses {
[MethodResponse::GetTransactionsWithProofs(ref txs)] => txs,
subresponses => {
return Err(Error::rpc_response(format!(
"expected [GetTransactionsWithProofs] subresponses, received: {:?}",
subresponses,
)))
}
};
let txs_with_proofs_view = if let Some(txs_with_proofs_view) = maybe_txs_with_proofs_view {
txs_with_proofs_view
} else {
return Ok(MethodResponse::GetTransactions(Vec::new()));
};
let has_events = txs_with_proofs_view.serialized_events.is_some();
if include_events != has_events {
return Err(Error::rpc_response(format!(
"expected events: {}, received events: {}",
include_events, has_events
)));
}
let txn_list_with_proof = txs_with_proofs_view
.try_into_txn_list_with_proof(start_version)
.map_err(Error::decode)?;
let latest_li = ctxt.state_proof.latest_ledger_info();
txn_list_with_proof
.verify(latest_li, Some(start_version))
.map_err(Error::invalid_proof)?;
let txn_list_view =
TransactionListView::try_from(txn_list_with_proof).map_err(Error::decode)?;
Ok(MethodResponse::GetTransactions(txn_list_view.0))
});
VerifyingRequest::new(request, subrequests, callback)
}
fn get_account_transactions(
address: AccountAddress,
start_seq_num: u64,
limit: u64,
include_events: bool,
) -> VerifyingRequest {
let request =
MethodRequest::GetAccountTransactions(address, start_seq_num, limit, include_events);
let subrequests = vec![MethodRequest::GetAccountTransactionsWithProofs(
address,
start_seq_num,
limit,
include_events,
None,
)];
let callback: RequestCallback = Box::new(move |ctxt, subresponses| {
let acct_txns_with_proof_view = match subresponses {
[MethodResponse::GetAccountTransactionsWithProofs(ref txs)] => txs,
subresponses => {
return Err(Error::rpc_response(format!(
"expected [GetAccountTransactionsWithProofs] subresponses, received: {:?}",
subresponses,
)))
}
};
let acct_txns_with_proof =
AccountTransactionsWithProof::try_from(acct_txns_with_proof_view)
.map_err(Error::decode)?;
let latest_li = ctxt.state_proof.latest_ledger_info();
let ledger_version = latest_li.version();
acct_txns_with_proof
.verify(
latest_li,
address,
start_seq_num,
limit,
include_events,
ledger_version,
)
.map_err(Error::invalid_proof)?;
let txs = TransactionListView::try_from(acct_txns_with_proof).map_err(Error::decode)?;
Ok(MethodResponse::GetAccountTransactions(txs.0))
});
VerifyingRequest::new(request, subrequests, callback)
}
fn get_account_transaction(
address: AccountAddress,
seq_num: u64,
include_events: bool,
) -> VerifyingRequest {
get_account_transactions(address, seq_num, 1, include_events).map(|_ctxt, response| {
match response {
MethodResponse::GetAccountTransactions(txns) => {
MethodResponse::GetAccountTransaction(txns.into_iter().next())
}
response => panic!(
"expected GetAccountTransactions response, got: {:?}",
response
),
}
})
}
fn get_events(key: EventKey, start_seq: u64, limit: u64) -> VerifyingRequest {
let request = MethodRequest::GetEvents(key, start_seq, limit);
let subrequests = vec![MethodRequest::GetEventsWithProofs(key, start_seq, limit)];
let callback: RequestCallback = Box::new(move |ctxt, subresponses| {
let event_with_proof_views = match subresponses {
[MethodResponse::GetEventsWithProofs(ref inner)] => inner,
subresponses => {
return Err(Error::rpc_response(format!(
"expected [GetEventsWithProofs] subresponses, received: {:?}",
subresponses,
)))
}
};
let num_received = event_with_proof_views.len() as u64;
if num_received > limit {
return Err(Error::rpc_response(format!(
"more events than limit: limit {} events, received {} events",
limit, num_received,
)));
}
let latest_li = ctxt.state_proof.latest_ledger_info();
let event_views = event_with_proof_views
.iter()
.enumerate()
.map(|(offset, event_with_proof_view)| {
let event_with_proof =
EventWithProof::try_from(event_with_proof_view).map_err(Error::decode)?;
let txn_version = event_with_proof.transaction_version;
event_with_proof
.verify(
latest_li,
&key,
start_seq + offset as u64,
txn_version,
event_with_proof.event_index,
)
.map_err(Error::invalid_proof)?;
let event_view = EventView::try_from((txn_version, event_with_proof.event))
.map_err(Error::decode)?;
Ok(event_view)
})
.collect::<Result<Vec<_>>>()?;
Ok(MethodResponse::GetEvents(event_views))
});
VerifyingRequest::new(request, subrequests, callback)
}
fn get_currencies() -> VerifyingRequest {
let request = MethodRequest::GetCurrencies([]);
let subrequests = vec![MethodRequest::GetAccountStateWithProof(
diem_root_address(),
None,
None,
)];
let callback: RequestCallback = Box::new(|ctxt, subresponses| {
let diem_root = match subresponses {
[MethodResponse::GetAccountStateWithProof(ref diem_root)] => diem_root,
subresponses => {
return Err(Error::rpc_response(format!(
"expected [GetAccountStateWithProof] subresponses, received: {:?}",
subresponses,
)))
}
};
let diem_root = verify_account_state(ctxt, diem_root, diem_root_address(), None)?
.ok_or_else(|| Error::rpc_response("DiemRoot account is missing"))?;
let currency_infos = diem_root
.get_registered_currency_info_resources()
.map_err(Error::decode)?;
let currency_views = currency_infos.iter().map(CurrencyInfoView::from).collect();
Ok(MethodResponse::GetCurrencies(currency_views))
});
VerifyingRequest::new(request, subrequests, callback)
}
fn get_network_status() -> VerifyingRequest {
let request = MethodRequest::get_network_status();
let subrequests = vec![MethodRequest::get_network_status()];
let callback: RequestCallback = Box::new(|_ctxt, subresponses| {
let status = match subresponses {
[MethodResponse::GetNetworkStatus(ref status)] => *status,
subresponses => {
return Err(Error::rpc_response(format!(
"expected [GetNetworkStatus] subresponses, received: {:?}",
subresponses,
)))
}
};
Ok(MethodResponse::GetNetworkStatus(status))
});
VerifyingRequest::new(request, subrequests, callback)
}
fn verify_account_state(
ctxt: RequestContext<'_>,
view: &AccountStateWithProofView,
address: AccountAddress,
version: Option<Version>,
) -> Result<Option<AccountState>> {
let account_state_with_proof = AccountStateWithProof::try_from(view).map_err(Error::decode)?;
let latest_li = ctxt.state_proof.latest_ledger_info();
let ledger_version = latest_li.version();
let version = version.unwrap_or(ledger_version);
account_state_with_proof
.verify(latest_li, version, address)
.map_err(Error::invalid_proof)?;
account_state_with_proof
.blob
.map(|blob| AccountState::try_from(&blob).map_err(Error::decode))
.transpose()
}