use crate::{
account_address::AccountAddress,
account_config::XUS_NAME,
account_state_blob::AccountStateBlob,
block_metadata::BlockMetadata,
chain_id::ChainId,
contract_event::ContractEvent,
ledger_info::LedgerInfo,
nibble::nibble_path::NibblePath,
proof::{
accumulator::InMemoryAccumulator, TransactionInfoListWithProof, TransactionInfoWithProof,
},
transaction::authenticator::{AccountAuthenticator, TransactionAuthenticator},
vm_status::{DiscardedVMStatus, KeptVMStatus, StatusCode, StatusType, VMStatus},
write_set::WriteSet,
};
use anyhow::{ensure, format_err, Error, Result};
use diem_crypto::{
ed25519::*,
hash::{CryptoHash, EventAccumulatorHasher},
multi_ed25519::{MultiEd25519PublicKey, MultiEd25519Signature},
traits::SigningKey,
HashValue,
};
use diem_crypto_derive::{BCSCryptoHash, CryptoHasher};
use move_core_types::transaction_argument::convert_txn_args;
#[cfg(any(test, feature = "fuzzing"))]
use proptest_derive::Arbitrary;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
collections::HashMap,
convert::TryFrom,
fmt,
fmt::{Debug, Display, Formatter},
};
pub mod authenticator;
mod change_set;
pub mod helpers;
pub mod metadata;
mod module;
mod script;
mod transaction_argument;
pub use change_set::ChangeSet;
pub use module::Module;
pub use script::{
ArgumentABI, Script, ScriptABI, ScriptFunction, ScriptFunctionABI, TransactionScriptABI,
TypeArgumentABI,
};
use std::{collections::BTreeSet, hash::Hash, ops::Deref};
pub use transaction_argument::{parse_transaction_argument, TransactionArgument, VecBytes};
pub type Version = u64;
pub const PRE_GENESIS_VERSION: Version = u64::max_value();
#[derive(
Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, CryptoHasher, BCSCryptoHash,
)]
pub struct RawTransaction {
sender: AccountAddress,
sequence_number: u64,
payload: TransactionPayload,
max_gas_amount: u64,
gas_unit_price: u64,
gas_currency_code: String,
expiration_timestamp_secs: u64,
chain_id: ChainId,
}
impl RawTransaction {
pub fn new(
sender: AccountAddress,
sequence_number: u64,
payload: TransactionPayload,
max_gas_amount: u64,
gas_unit_price: u64,
gas_currency_code: String,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload,
max_gas_amount,
gas_unit_price,
gas_currency_code,
expiration_timestamp_secs,
chain_id,
}
}
pub fn new_script(
sender: AccountAddress,
sequence_number: u64,
script: Script,
max_gas_amount: u64,
gas_unit_price: u64,
gas_currency_code: String,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::Script(script),
max_gas_amount,
gas_unit_price,
gas_currency_code,
expiration_timestamp_secs,
chain_id,
}
}
pub fn new_script_function(
sender: AccountAddress,
sequence_number: u64,
script_function: ScriptFunction,
max_gas_amount: u64,
gas_unit_price: u64,
gas_currency_code: String,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::ScriptFunction(script_function),
max_gas_amount,
gas_unit_price,
gas_currency_code,
expiration_timestamp_secs,
chain_id,
}
}
pub fn new_module(
sender: AccountAddress,
sequence_number: u64,
module: Module,
max_gas_amount: u64,
gas_unit_price: u64,
gas_currency_code: String,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::Module(module),
max_gas_amount,
gas_unit_price,
gas_currency_code,
expiration_timestamp_secs,
chain_id,
}
}
pub fn new_write_set(
sender: AccountAddress,
sequence_number: u64,
write_set: WriteSet,
chain_id: ChainId,
) -> Self {
Self::new_change_set(
sender,
sequence_number,
ChangeSet::new(write_set, vec![]),
chain_id,
)
}
pub fn new_change_set(
sender: AccountAddress,
sequence_number: u64,
change_set: ChangeSet,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::WriteSet(WriteSetPayload::Direct(change_set)),
max_gas_amount: 0,
gas_unit_price: 0,
gas_currency_code: XUS_NAME.to_owned(),
expiration_timestamp_secs: u64::max_value(),
chain_id,
}
}
pub fn new_writeset_script(
sender: AccountAddress,
sequence_number: u64,
script: Script,
signer: AccountAddress,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::WriteSet(WriteSetPayload::Script {
execute_as: signer,
script,
}),
max_gas_amount: 0,
gas_unit_price: 0,
gas_currency_code: XUS_NAME.to_owned(),
expiration_timestamp_secs: u64::max_value(),
chain_id,
}
}
pub fn sign(
self,
private_key: &Ed25519PrivateKey,
public_key: Ed25519PublicKey,
) -> Result<SignatureCheckedTransaction> {
let signature = private_key.sign(&self);
Ok(SignatureCheckedTransaction(SignedTransaction::new(
self, public_key, signature,
)))
}
pub fn sign_multi_agent(
self,
sender_private_key: &Ed25519PrivateKey,
secondary_signers: Vec<AccountAddress>,
secondary_private_keys: Vec<&Ed25519PrivateKey>,
) -> Result<SignatureCheckedTransaction> {
let message =
RawTransactionWithData::new_multi_agent(self.clone(), secondary_signers.clone());
let sender_signature = sender_private_key.sign(&message);
let sender_authenticator = AccountAuthenticator::ed25519(
Ed25519PublicKey::from(sender_private_key),
sender_signature,
);
if secondary_private_keys.len() != secondary_signers.len() {
return Err(format_err!(
"number of secondary private keys and number of secondary signers don't match"
));
}
let mut secondary_authenticators = vec![];
for priv_key in secondary_private_keys {
let signature = priv_key.sign(&message);
secondary_authenticators.push(AccountAuthenticator::ed25519(
Ed25519PublicKey::from(priv_key),
signature,
));
}
Ok(SignatureCheckedTransaction(
SignedTransaction::new_multi_agent(
self,
sender_authenticator,
secondary_signers,
secondary_authenticators,
),
))
}
#[cfg(any(test, feature = "fuzzing"))]
pub fn multi_sign_for_testing(
self,
private_key: &Ed25519PrivateKey,
public_key: Ed25519PublicKey,
) -> Result<SignatureCheckedTransaction> {
let signature = private_key.sign(&self);
Ok(SignatureCheckedTransaction(
SignedTransaction::new_multisig(self, public_key.into(), signature.into()),
))
}
pub fn into_payload(self) -> TransactionPayload {
self.payload
}
pub fn format_for_client(&self, get_transaction_name: impl Fn(&[u8]) -> String) -> String {
let (code, args) = match &self.payload {
TransactionPayload::WriteSet(_) => ("genesis".to_string(), vec![]),
TransactionPayload::Script(script) => (
get_transaction_name(script.code()),
convert_txn_args(script.args()),
),
TransactionPayload::ScriptFunction(script_fn) => (
format!("{}::{}", script_fn.module(), script_fn.function()),
script_fn.args().to_vec(),
),
TransactionPayload::Module(_) => ("module publishing".to_string(), vec![]),
};
let mut f_args: String = "".to_string();
for arg in args {
f_args = format!("{}\n\t\t\t{:02X?},", f_args, arg);
}
format!(
"RawTransaction {{ \n\
\tsender: {}, \n\
\tsequence_number: {}, \n\
\tpayload: {{, \n\
\t\ttransaction: {}, \n\
\t\targs: [ {} \n\
\t\t]\n\
\t}}, \n\
\tmax_gas_amount: {}, \n\
\tgas_unit_price: {}, \n\
\tgas_currency_code: {}, \n\
\texpiration_timestamp_secs: {:#?}, \n\
\tchain_id: {},
}}",
self.sender,
self.sequence_number,
code,
f_args,
self.max_gas_amount,
self.gas_unit_price,
self.gas_currency_code,
self.expiration_timestamp_secs,
self.chain_id,
)
}
pub fn sender(&self) -> AccountAddress {
self.sender
}
}
#[derive(
Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, CryptoHasher, BCSCryptoHash,
)]
pub enum RawTransactionWithData {
MultiAgent {
raw_txn: RawTransaction,
secondary_signer_addresses: Vec<AccountAddress>,
},
}
impl RawTransactionWithData {
pub fn new_multi_agent(
raw_txn: RawTransaction,
secondary_signer_addresses: Vec<AccountAddress>,
) -> Self {
Self::MultiAgent {
raw_txn,
secondary_signer_addresses,
}
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum TransactionPayload {
WriteSet(WriteSetPayload),
Script(Script),
Module(Module),
ScriptFunction(ScriptFunction),
}
impl TransactionPayload {
pub fn should_trigger_reconfiguration_by_default(&self) -> bool {
match self {
Self::WriteSet(ws) => ws.should_trigger_reconfiguration_by_default(),
Self::Script(_) | Self::ScriptFunction(_) | Self::Module(_) => false,
}
}
pub fn into_script_function(self) -> ScriptFunction {
match self {
Self::ScriptFunction(f) => f,
payload => panic!("Expected ScriptFunction(_) payload, found: {:#?}", payload),
}
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum WriteSetPayload {
Direct(ChangeSet),
Script {
execute_as: AccountAddress,
script: Script,
},
}
impl WriteSetPayload {
pub fn should_trigger_reconfiguration_by_default(&self) -> bool {
match self {
Self::Direct(_) => true,
Self::Script { .. } => false,
}
}
}
#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct SignedTransaction {
raw_txn: RawTransaction,
authenticator: TransactionAuthenticator,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct SignatureCheckedTransaction(SignedTransaction);
impl SignatureCheckedTransaction {
pub fn into_inner(self) -> SignedTransaction {
self.0
}
pub fn into_raw_transaction(self) -> RawTransaction {
self.0.into_raw_transaction()
}
}
impl Deref for SignatureCheckedTransaction {
type Target = SignedTransaction;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Debug for SignedTransaction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"SignedTransaction {{ \n \
{{ raw_txn: {:#?}, \n \
authenticator: {:#?}, \n \
}} \n \
}}",
self.raw_txn, self.authenticator
)
}
}
impl SignedTransaction {
pub fn new(
raw_txn: RawTransaction,
public_key: Ed25519PublicKey,
signature: Ed25519Signature,
) -> SignedTransaction {
let authenticator = TransactionAuthenticator::ed25519(public_key, signature);
SignedTransaction {
raw_txn,
authenticator,
}
}
pub fn new_multisig(
raw_txn: RawTransaction,
public_key: MultiEd25519PublicKey,
signature: MultiEd25519Signature,
) -> SignedTransaction {
let authenticator = TransactionAuthenticator::multi_ed25519(public_key, signature);
SignedTransaction {
raw_txn,
authenticator,
}
}
pub fn new_multi_agent(
raw_txn: RawTransaction,
sender: AccountAuthenticator,
secondary_signer_addresses: Vec<AccountAddress>,
secondary_signers: Vec<AccountAuthenticator>,
) -> Self {
SignedTransaction {
raw_txn,
authenticator: TransactionAuthenticator::multi_agent(
sender,
secondary_signer_addresses,
secondary_signers,
),
}
}
pub fn authenticator(&self) -> TransactionAuthenticator {
self.authenticator.clone()
}
pub fn sender(&self) -> AccountAddress {
self.raw_txn.sender
}
pub fn into_raw_transaction(self) -> RawTransaction {
self.raw_txn
}
pub fn sequence_number(&self) -> u64 {
self.raw_txn.sequence_number
}
pub fn chain_id(&self) -> ChainId {
self.raw_txn.chain_id
}
pub fn payload(&self) -> &TransactionPayload {
&self.raw_txn.payload
}
pub fn max_gas_amount(&self) -> u64 {
self.raw_txn.max_gas_amount
}
pub fn gas_unit_price(&self) -> u64 {
self.raw_txn.gas_unit_price
}
pub fn gas_currency_code(&self) -> &str {
&self.raw_txn.gas_currency_code
}
pub fn expiration_timestamp_secs(&self) -> u64 {
self.raw_txn.expiration_timestamp_secs
}
pub fn raw_txn_bytes_len(&self) -> usize {
bcs::to_bytes(&self.raw_txn)
.expect("Unable to serialize RawTransaction")
.len()
}
pub fn check_signature(self) -> Result<SignatureCheckedTransaction> {
self.authenticator.verify(&self.raw_txn)?;
Ok(SignatureCheckedTransaction(self))
}
pub fn contains_duplicate_signers(&self) -> bool {
let mut all_signer_addresses = self.authenticator.secondary_signer_addreses();
all_signer_addresses.push(self.sender());
let mut s = BTreeSet::new();
all_signer_addresses.iter().any(|a| !s.insert(*a))
}
pub fn format_for_client(&self, get_transaction_name: impl Fn(&[u8]) -> String) -> String {
format!(
"SignedTransaction {{ \n \
raw_txn: {}, \n \
authenticator: {:#?}, \n \
}}",
self.raw_txn.format_for_client(get_transaction_name),
self.authenticator
)
}
pub fn is_multi_agent(&self) -> bool {
matches!(
self.authenticator,
TransactionAuthenticator::MultiAgent { .. }
)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct TransactionWithProof<T> {
pub version: Version,
pub transaction: Transaction,
pub events: Option<Vec<ContractEvent>>,
pub proof: TransactionInfoWithProof<T>,
}
impl<T: TransactionInfoTrait> TransactionWithProof<T> {
pub fn new(
version: Version,
transaction: Transaction,
events: Option<Vec<ContractEvent>>,
proof: TransactionInfoWithProof<T>,
) -> Self {
Self {
version,
transaction,
events,
proof,
}
}
pub fn verify_user_txn(
&self,
ledger_info: &LedgerInfo,
version: Version,
sender: AccountAddress,
sequence_number: u64,
) -> Result<()> {
let signed_transaction = self.transaction.as_signed_user_txn()?;
ensure!(
self.version == version,
"Version ({}) is not expected ({}).",
self.version,
version,
);
ensure!(
signed_transaction.sender() == sender,
"Sender ({}) not expected ({}).",
signed_transaction.sender(),
sender,
);
ensure!(
signed_transaction.sequence_number() == sequence_number,
"Sequence number ({}) not expected ({}).",
signed_transaction.sequence_number(),
sequence_number,
);
let txn_hash = self.transaction.hash();
ensure!(
txn_hash == self.proof.transaction_info().transaction_hash(),
"Transaction hash ({}) not expected ({}).",
txn_hash,
self.proof.transaction_info().transaction_hash(),
);
if let Some(events) = &self.events {
let event_hashes: Vec<_> = events.iter().map(CryptoHash::hash).collect();
let event_root_hash =
InMemoryAccumulator::<EventAccumulatorHasher>::from_leaves(&event_hashes[..])
.root_hash();
ensure!(
event_root_hash == self.proof.transaction_info().event_root_hash(),
"Event root hash ({}) not expected ({}).",
event_root_hash,
self.proof.transaction_info().event_root_hash(),
);
}
self.proof.verify(ledger_info, version)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum TransactionStatus {
Discard(DiscardedVMStatus),
Keep(KeptVMStatus),
Retry,
}
impl TransactionStatus {
pub fn status(&self) -> Result<KeptVMStatus, StatusCode> {
match self {
TransactionStatus::Keep(status) => Ok(status.clone()),
TransactionStatus::Discard(code) => Err(*code),
TransactionStatus::Retry => Err(StatusCode::UNKNOWN_VALIDATION_STATUS),
}
}
pub fn is_discarded(&self) -> bool {
match self {
TransactionStatus::Discard(_) => true,
TransactionStatus::Keep(_) => false,
TransactionStatus::Retry => true,
}
}
}
impl From<VMStatus> for TransactionStatus {
fn from(vm_status: VMStatus) -> Self {
match vm_status.keep_or_discard() {
Ok(recorded) => TransactionStatus::Keep(recorded),
Err(code) => TransactionStatus::Discard(code),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum GovernanceRole {
DiemRoot,
TreasuryCompliance,
Validator,
ValidatorOperator,
DesignatedDealer,
NonGovernanceRole,
}
impl GovernanceRole {
pub fn from_role_id(role_id: u64) -> Self {
use GovernanceRole::*;
match role_id {
0 => DiemRoot,
1 => TreasuryCompliance,
2 => DesignatedDealer,
3 => Validator,
4 => ValidatorOperator,
_ => NonGovernanceRole,
}
}
pub fn priority(&self) -> u64 {
use GovernanceRole::*;
match self {
DiemRoot => 3,
TreasuryCompliance => 2,
Validator | ValidatorOperator | DesignatedDealer => 1,
NonGovernanceRole => 0,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VMValidatorResult {
status: Option<DiscardedVMStatus>,
score: u64,
governance_role: GovernanceRole,
}
impl VMValidatorResult {
pub fn new(
vm_status: Option<DiscardedVMStatus>,
score: u64,
governance_role: GovernanceRole,
) -> Self {
debug_assert!(
match vm_status {
None => true,
Some(status) =>
status.status_type() == StatusType::Unknown
|| status.status_type() == StatusType::Validation
|| status.status_type() == StatusType::InvariantViolation,
},
"Unexpected discarded status: {:?}",
vm_status
);
Self {
status: vm_status,
score,
governance_role,
}
}
pub fn error(vm_status: DiscardedVMStatus) -> Self {
Self {
status: Some(vm_status),
score: 0,
governance_role: GovernanceRole::NonGovernanceRole,
}
}
pub fn status(&self) -> Option<DiscardedVMStatus> {
self.status
}
pub fn score(&self) -> u64 {
self.score
}
pub fn governance_role(&self) -> GovernanceRole {
self.governance_role
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TransactionOutput {
write_set: WriteSet,
events: Vec<ContractEvent>,
gas_used: u64,
status: TransactionStatus,
}
impl TransactionOutput {
pub fn new(
write_set: WriteSet,
events: Vec<ContractEvent>,
gas_used: u64,
status: TransactionStatus,
) -> Self {
TransactionOutput {
write_set,
events,
gas_used,
status,
}
}
pub fn into(self) -> (WriteSet, Vec<ContractEvent>) {
(self.write_set, self.events)
}
pub fn write_set(&self) -> &WriteSet {
&self.write_set
}
pub fn events(&self) -> &[ContractEvent] {
&self.events
}
pub fn gas_used(&self) -> u64 {
self.gas_used
}
pub fn status(&self) -> &TransactionStatus {
&self.status
}
}
pub trait TransactionInfoTrait:
Clone + CryptoHash + Debug + Display + Eq + Serialize + DeserializeOwned
{
fn new(
transaction_hash: HashValue,
state_root_hash: HashValue,
event_root_hash: HashValue,
gas_used: u64,
status: KeptVMStatus,
) -> Self;
fn transaction_hash(&self) -> HashValue;
fn state_root_hash(&self) -> HashValue;
fn event_root_hash(&self) -> HashValue;
fn gas_used(&self) -> u64;
fn status(&self) -> &KeptVMStatus;
}
#[derive(Clone, CryptoHasher, BCSCryptoHash, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct TransactionInfo {
transaction_hash: HashValue,
state_root_hash: HashValue,
event_root_hash: HashValue,
gas_used: u64,
status: KeptVMStatus,
}
impl TransactionInfoTrait for TransactionInfo {
fn new(
transaction_hash: HashValue,
state_root_hash: HashValue,
event_root_hash: HashValue,
gas_used: u64,
status: KeptVMStatus,
) -> TransactionInfo {
TransactionInfo {
transaction_hash,
state_root_hash,
event_root_hash,
gas_used,
status,
}
}
fn transaction_hash(&self) -> HashValue {
self.transaction_hash
}
fn state_root_hash(&self) -> HashValue {
self.state_root_hash
}
fn event_root_hash(&self) -> HashValue {
self.event_root_hash
}
fn gas_used(&self) -> u64 {
self.gas_used
}
fn status(&self) -> &KeptVMStatus {
&self.status
}
}
impl Display for TransactionInfo {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"TransactionInfo: [txn_hash: {}, state_root_hash: {}, event_root_hash: {}, gas_used: {}, recorded_status: {:?}]",
self.transaction_hash(), self.state_root_hash(), self.event_root_hash(), self.gas_used(), self.status(),
)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct TransactionToCommit {
transaction: Transaction,
account_states: HashMap<AccountAddress, AccountStateBlob>,
jf_node_hashes: Option<HashMap<NibblePath, HashValue>>,
events: Vec<ContractEvent>,
gas_used: u64,
status: KeptVMStatus,
}
impl TransactionToCommit {
pub fn new(
transaction: Transaction,
account_states: HashMap<AccountAddress, AccountStateBlob>,
jf_node_hashes: Option<HashMap<NibblePath, HashValue>>,
events: Vec<ContractEvent>,
gas_used: u64,
status: KeptVMStatus,
) -> Self {
TransactionToCommit {
transaction,
account_states,
jf_node_hashes,
events,
gas_used,
status,
}
}
pub fn transaction(&self) -> &Transaction {
&self.transaction
}
pub fn account_states(&self) -> &HashMap<AccountAddress, AccountStateBlob> {
&self.account_states
}
pub fn jf_node_hashes(&self) -> Option<&HashMap<NibblePath, HashValue>> {
self.jf_node_hashes.as_ref()
}
pub fn events(&self) -> &[ContractEvent] {
&self.events
}
pub fn gas_used(&self) -> u64 {
self.gas_used
}
pub fn status(&self) -> &KeptVMStatus {
&self.status
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct TransactionListWithProof<T> {
pub transactions: Vec<Transaction>,
pub events: Option<Vec<Vec<ContractEvent>>>,
pub first_transaction_version: Option<Version>,
pub proof: TransactionInfoListWithProof<T>,
}
impl<T: TransactionInfoTrait> TransactionListWithProof<T> {
pub fn new(
transactions: Vec<Transaction>,
events: Option<Vec<Vec<ContractEvent>>>,
first_transaction_version: Option<Version>,
proof: TransactionInfoListWithProof<T>,
) -> Self {
Self {
transactions,
events,
first_transaction_version,
proof,
}
}
pub fn new_empty() -> Self {
Self::new(
vec![],
None,
None,
TransactionInfoListWithProof::new_empty(),
)
}
pub fn verify(
&self,
ledger_info: &LedgerInfo,
first_transaction_version: Option<Version>,
) -> Result<()> {
ensure!(
self.first_transaction_version == first_transaction_version,
"First transaction version ({:?}) doesn't match given version ({:?}).",
self.first_transaction_version,
first_transaction_version,
);
ensure!(
self.proof.transaction_infos.len() == self.transactions.len(),
"The number of TransactionInfo objects ({}) does not match the number of \
transactions ({}).",
self.proof.transaction_infos.len(),
self.transactions.len(),
);
let transaction_hashes: Vec<_> = self.transactions.iter().map(CryptoHash::hash).collect();
itertools::zip_eq(transaction_hashes, &self.proof.transaction_infos)
.map(|(txn_hash, txn_info)| {
ensure!(
txn_hash == txn_info.transaction_hash(),
"The hash of transaction does not match the transaction info in proof. \
Transaction hash: {:x}. Transaction hash in txn_info: {:x}.",
txn_hash,
txn_info.transaction_hash(),
);
Ok(())
})
.collect::<Result<Vec<_>>>()?;
self.proof
.verify(ledger_info, self.first_transaction_version)?;
if let Some(event_lists) = &self.events {
ensure!(
event_lists.len() == self.transactions.len(),
"The length of event_lists ({}) does not match the number of transactions ({}).",
event_lists.len(),
self.transactions.len(),
);
itertools::zip_eq(event_lists, &self.proof.transaction_infos)
.map(|(events, txn_info)| verify_events_against_root_hash(events, txn_info))
.collect::<Result<Vec<_>>>()?;
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct TransactionOutputListWithProof<T> {
pub transaction_outputs: Vec<TransactionOutput>,
pub first_transaction_output_version: Option<Version>,
pub proof: TransactionInfoListWithProof<T>,
}
impl<T: TransactionInfoTrait> TransactionOutputListWithProof<T> {
pub fn new(
transaction_outputs: Vec<TransactionOutput>,
first_transaction_output_version: Option<Version>,
proof: TransactionInfoListWithProof<T>,
) -> Self {
Self {
transaction_outputs,
first_transaction_output_version,
proof,
}
}
pub fn new_empty() -> Self {
Self::new(vec![], None, TransactionInfoListWithProof::new_empty())
}
pub fn verify(
&self,
ledger_info: &LedgerInfo,
first_transaction_output_version: Option<Version>,
) -> Result<()> {
ensure!(
self.first_transaction_output_version == first_transaction_output_version,
"First transaction output version ({:?}) doesn't match given version ({:?}).",
self.first_transaction_output_version,
first_transaction_output_version,
);
self.proof
.verify(ledger_info, self.first_transaction_output_version)?;
itertools::zip_eq(&self.transaction_outputs, &self.proof.transaction_infos)
.map(|(txn_output, txn_info)| {
verify_events_against_root_hash(&txn_output.events, txn_info)
})
.collect::<Result<Vec<_>>>()?;
Ok(())
}
}
fn verify_events_against_root_hash<T: TransactionInfoTrait>(
events: &[ContractEvent],
transaction_info: &T,
) -> Result<()> {
let event_hashes: Vec<_> = events.iter().map(CryptoHash::hash).collect();
let event_root_hash =
InMemoryAccumulator::<EventAccumulatorHasher>::from_leaves(&event_hashes).root_hash();
ensure!(
event_root_hash == transaction_info.event_root_hash(),
"The event root hash calculated doesn't match that carried on the \
transaction info! Calculated hash {:?}, transaction info hash {:?}",
event_root_hash,
transaction_info.event_root_hash()
);
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct AccountTransactionsWithProof<T>(pub Vec<TransactionWithProof<T>>);
impl<T: TransactionInfoTrait> AccountTransactionsWithProof<T> {
pub fn new(txns_with_proofs: Vec<TransactionWithProof<T>>) -> Self {
Self(txns_with_proofs)
}
pub fn new_empty() -> Self {
Self::new(Vec::new())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn inner(&self) -> &[TransactionWithProof<T>] {
&self.0
}
pub fn into_inner(self) -> Vec<TransactionWithProof<T>> {
self.0
}
pub fn verify(
&self,
ledger_info: &LedgerInfo,
account: AccountAddress,
start_seq_num: u64,
limit: u64,
include_events: bool,
ledger_version: Version,
) -> Result<()> {
ensure!(
self.len() as u64 <= limit,
"number of account transactions ({}) exceeded limit ({})",
self.len(),
limit,
);
self.0
.iter()
.enumerate()
.try_for_each(|(seq_num_offset, txn_with_proof)| {
let expected_seq_num = start_seq_num.saturating_add(seq_num_offset as u64);
let txn_version = txn_with_proof.version;
ensure!(
include_events == txn_with_proof.events.is_some(),
"unexpected events or missing events"
);
ensure!(
txn_version <= ledger_version,
"transaction with version ({}) greater than requested ledger version ({})",
txn_version,
ledger_version,
);
txn_with_proof.verify_user_txn(ledger_info, txn_version, account, expected_seq_num)
})
}
}
#[allow(clippy::large_enum_variant)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, CryptoHasher, BCSCryptoHash)]
pub enum Transaction {
UserTransaction(SignedTransaction),
GenesisTransaction(WriteSetPayload),
BlockMetadata(BlockMetadata),
}
impl Transaction {
pub fn as_signed_user_txn(&self) -> Result<&SignedTransaction> {
match self {
Transaction::UserTransaction(txn) => Ok(txn),
_ => Err(format_err!("Not a user transaction.")),
}
}
pub fn format_for_client(&self, get_transaction_name: impl Fn(&[u8]) -> String) -> String {
match self {
Transaction::UserTransaction(user_txn) => {
user_txn.format_for_client(get_transaction_name)
}
Transaction::GenesisTransaction(_write_set) => String::from("genesis"),
Transaction::BlockMetadata(_block_metadata) => String::from("block_metadata"),
}
}
}
impl TryFrom<Transaction> for SignedTransaction {
type Error = Error;
fn try_from(txn: Transaction) -> Result<Self> {
match txn {
Transaction::UserTransaction(txn) => Ok(txn),
_ => Err(format_err!("Not a user transaction.")),
}
}
}
pub mod default_protocol {
use super::TransactionInfo;
pub type AccountTransactionsWithProof = super::AccountTransactionsWithProof<TransactionInfo>;
pub type TransactionInfoWithProof = super::TransactionInfoWithProof<TransactionInfo>;
pub type TransactionListWithProof = super::TransactionListWithProof<TransactionInfo>;
pub type TransactionOutputListWithProof =
super::TransactionOutputListWithProof<TransactionInfo>;
pub type TransactionWithProof = super::TransactionWithProof<TransactionInfo>;
}