use crate::{
account_address::AccountAddress,
block_metadata::BlockMetadata,
chain_id::ChainId,
contract_event::ContractEvent,
ledger_info::LedgerInfo,
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 aptos_crypto::{
ed25519::*,
hash::{CryptoHash, EventAccumulatorHasher},
multi_ed25519::{MultiEd25519PublicKey, MultiEd25519Signature},
traits::{signing_message, SigningKey},
HashValue,
};
use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher};
use move_deps::move_core_types::transaction_argument::convert_txn_args;
#[cfg(any(test, feature = "fuzzing"))]
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
convert::TryFrom,
fmt,
fmt::{Debug, Display, Formatter},
};
pub mod authenticator;
mod change_set;
mod module;
mod script;
mod transaction_argument;
pub use change_set::ChangeSet;
pub use module::{Module, ModuleBundle};
pub use script::{
ArgumentABI, Script, ScriptABI, ScriptFunction, ScriptFunctionABI, TransactionScriptABI,
TypeArgumentABI,
};
use crate::state_store::{state_key::StateKey, state_value::StateValue};
use move_deps::move_core_types::vm_status::AbortLocation;
use std::{collections::BTreeSet, hash::Hash, ops::Deref, sync::atomic::AtomicU64};
pub use transaction_argument::{parse_transaction_argument, TransactionArgument};
pub type Version = u64; pub type AtomicVersion = AtomicU64;
#[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,
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,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload,
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn new_script(
sender: AccountAddress,
sequence_number: u64,
script: Script,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::Script(script),
max_gas_amount,
gas_unit_price,
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,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::ScriptFunction(script_function),
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn new_module(
sender: AccountAddress,
sequence_number: u64,
module: Module,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::ModuleBundle(ModuleBundle::from(module)),
max_gas_amount,
gas_unit_price,
expiration_timestamp_secs,
chain_id,
}
}
pub fn new_module_bundle(
sender: AccountAddress,
sequence_number: u64,
modules: ModuleBundle,
max_gas_amount: u64,
gas_unit_price: u64,
expiration_timestamp_secs: u64,
chain_id: ChainId,
) -> Self {
RawTransaction {
sender,
sequence_number,
payload: TransactionPayload::ModuleBundle(modules),
max_gas_amount,
gas_unit_price,
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,
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,
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::ModuleBundle(_) => ("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\
\texpiration_timestamp_secs: {:#?}, \n\
\tchain_id: {},
}}",
self.sender,
self.sequence_number,
code,
f_args,
self.max_gas_amount,
self.gas_unit_price,
self.expiration_timestamp_secs,
self.chain_id,
)
}
pub fn sender(&self) -> AccountAddress {
self.sender
}
pub fn signing_message(&self) -> Vec<u8> {
signing_message(self)
}
}
#[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),
ModuleBundle(ModuleBundle),
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::ModuleBundle(_) => 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 new_with_authenticator(
raw_txn: RawTransaction,
authenticator: TransactionAuthenticator,
) -> Self {
Self {
raw_txn,
authenticator,
}
}
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 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 { .. }
)
}
pub fn committed_hash(self) -> HashValue {
Transaction::UserTransaction(self).hash()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct TransactionWithProof {
pub version: Version,
pub transaction: Transaction,
pub events: Option<Vec<ContractEvent>>,
pub proof: TransactionInfoWithProof,
}
impl TransactionWithProof {
pub fn new(
version: Version,
transaction: Transaction,
events: Option<Vec<ContractEvent>>,
proof: TransactionInfoWithProof,
) -> 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, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
#[cfg_attr(any(test, feature = "fuzzing"), proptest(no_params))]
pub enum ExecutionStatus {
Success,
OutOfGas,
MoveAbort {
location: AbortLocation,
code: u64,
},
ExecutionFailure {
location: AbortLocation,
function: u16,
code_offset: u16,
},
MiscellaneousError(Option<StatusCode>),
}
impl From<KeptVMStatus> for ExecutionStatus {
fn from(kept_status: KeptVMStatus) -> Self {
match kept_status {
KeptVMStatus::Executed => ExecutionStatus::Success,
KeptVMStatus::OutOfGas => ExecutionStatus::OutOfGas,
KeptVMStatus::MoveAbort(location, code) => {
ExecutionStatus::MoveAbort { location, code }
}
KeptVMStatus::ExecutionFailure {
location: loc,
function: func,
code_offset: offset,
} => ExecutionStatus::ExecutionFailure {
location: loc,
function: func,
code_offset: offset,
},
KeptVMStatus::MiscellaneousError => ExecutionStatus::MiscellaneousError(None),
}
}
}
impl ExecutionStatus {
pub fn is_success(&self) -> bool {
matches!(self, ExecutionStatus::Success)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum TransactionStatus {
Discard(DiscardedVMStatus),
Keep(ExecutionStatus),
Retry,
}
impl TransactionStatus {
pub fn status(&self) -> Result<ExecutionStatus, 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,
}
}
pub fn as_kept_status(&self) -> Result<ExecutionStatus> {
match self {
TransactionStatus::Keep(s) => Ok(s.clone()),
_ => Err(format_err!("Not Keep.")),
}
}
}
impl From<VMStatus> for TransactionStatus {
fn from(vm_status: VMStatus) -> Self {
let status_code = vm_status.status_code();
match vm_status.keep_or_discard() {
Ok(recorded) => match recorded {
KeptVMStatus::MiscellaneousError => {
TransactionStatus::Keep(ExecutionStatus::MiscellaneousError(Some(status_code)))
}
_ => TransactionStatus::Keep(recorded.into()),
},
Err(code) => TransactionStatus::Discard(code),
}
}
}
impl From<ExecutionStatus> for TransactionStatus {
fn from(txn_execution_status: ExecutionStatus) -> Self {
TransactionStatus::Keep(txn_execution_status)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VMValidatorResult {
status: Option<DiscardedVMStatus>,
score: u64,
}
impl VMValidatorResult {
pub fn new(vm_status: Option<DiscardedVMStatus>, score: u64) -> 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,
}
}
pub fn error(vm_status: DiscardedVMStatus) -> Self {
Self {
status: Some(vm_status),
score: 0,
}
}
pub fn status(&self) -> Option<DiscardedVMStatus> {
self.status
}
pub fn score(&self) -> u64 {
self.score
}
}
#[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 fn unpack(self) -> (WriteSet, Vec<ContractEvent>, u64, TransactionStatus) {
let Self {
write_set,
events,
gas_used,
status,
} = self;
(write_set, events, gas_used, status)
}
}
#[derive(Clone, CryptoHasher, BCSCryptoHash, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub enum TransactionInfo {
V0(TransactionInfoV0),
}
impl TransactionInfo {
pub fn new(
transaction_hash: HashValue,
state_change_hash: HashValue,
event_root_hash: HashValue,
state_checkpoint_hash: Option<HashValue>,
gas_used: u64,
status: ExecutionStatus,
) -> Self {
Self::V0(TransactionInfoV0::new(
transaction_hash,
state_change_hash,
event_root_hash,
state_checkpoint_hash,
gas_used,
status,
))
}
#[cfg(any(test, feature = "fuzzing"))]
pub fn new_placeholder(
gas_used: u64,
state_checkpoint_hash: Option<HashValue>,
status: ExecutionStatus,
) -> Self {
Self::new(
HashValue::default(),
HashValue::default(),
HashValue::default(),
state_checkpoint_hash,
gas_used,
status,
)
}
}
impl Deref for TransactionInfo {
type Target = TransactionInfoV0;
fn deref(&self) -> &Self::Target {
match self {
Self::V0(txn_info) => txn_info,
}
}
}
#[derive(Clone, CryptoHasher, BCSCryptoHash, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct TransactionInfoV0 {
gas_used: u64,
status: ExecutionStatus,
transaction_hash: HashValue,
event_root_hash: HashValue,
state_change_hash: HashValue,
state_checkpoint_hash: Option<HashValue>,
}
impl TransactionInfoV0 {
pub fn new(
transaction_hash: HashValue,
state_change_hash: HashValue,
event_root_hash: HashValue,
state_checkpoint_hash: Option<HashValue>,
gas_used: u64,
status: ExecutionStatus,
) -> Self {
Self {
gas_used,
status,
transaction_hash,
event_root_hash,
state_change_hash,
state_checkpoint_hash,
}
}
pub fn transaction_hash(&self) -> HashValue {
self.transaction_hash
}
pub fn state_change_hash(&self) -> HashValue {
self.state_change_hash
}
pub fn is_state_checkpoint(&self) -> bool {
self.state_checkpoint_hash().is_some()
}
pub fn state_checkpoint_hash(&self) -> Option<HashValue> {
self.state_checkpoint_hash
}
pub fn ensure_state_checkpoint_hash(&self) -> Result<HashValue> {
self.state_checkpoint_hash
.ok_or_else(|| format_err!("State checkpoint hash not present in TransactionInfo"))
}
pub fn event_root_hash(&self) -> HashValue {
self.event_root_hash
}
pub fn gas_used(&self) -> u64 {
self.gas_used
}
pub fn status(&self) -> &ExecutionStatus {
&self.status
}
}
impl Display for TransactionInfo {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"TransactionInfo: [txn_hash: {}, state_change_hash: {}, event_root_hash: {}, state_checkpoint_hash: {:?}, gas_used: {}, recorded_status: {:?}]",
self.transaction_hash(), self.state_change_hash(), self.event_root_hash(), self.state_checkpoint_hash(), self.gas_used(), self.status(),
)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct TransactionToCommit {
transaction: Transaction,
transaction_info: TransactionInfo,
state_updates: HashMap<StateKey, StateValue>,
write_set: WriteSet,
events: Vec<ContractEvent>,
is_reconfig: bool,
}
impl TransactionToCommit {
pub fn new(
transaction: Transaction,
transaction_info: TransactionInfo,
state_updates: HashMap<StateKey, StateValue>,
write_set: WriteSet,
events: Vec<ContractEvent>,
is_reconfig: bool,
) -> Self {
TransactionToCommit {
transaction,
transaction_info,
state_updates,
write_set,
events,
is_reconfig,
}
}
pub fn transaction(&self) -> &Transaction {
&self.transaction
}
pub fn transaction_info(&self) -> &TransactionInfo {
&self.transaction_info
}
pub fn is_state_checkpoint(&self) -> bool {
self.transaction_info().is_state_checkpoint()
}
#[cfg(any(test, feature = "fuzzing"))]
pub fn set_transaction_info(&mut self, txn_info: TransactionInfo) {
self.transaction_info = txn_info
}
pub fn state_updates(&self) -> &HashMap<StateKey, StateValue> {
&self.state_updates
}
pub fn write_set(&self) -> &WriteSet {
&self.write_set
}
pub fn events(&self) -> &[ContractEvent] {
&self.events
}
pub fn gas_used(&self) -> u64 {
self.transaction_info.gas_used
}
pub fn status(&self) -> &ExecutionStatus {
&self.transaction_info.status
}
pub fn is_reconfig(&self) -> bool {
self.is_reconfig
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct TransactionListWithProof {
pub transactions: Vec<Transaction>,
pub events: Option<Vec<Vec<ContractEvent>>>,
pub first_transaction_version: Option<Version>,
pub proof: TransactionInfoListWithProof,
}
impl TransactionListWithProof {
pub fn new(
transactions: Vec<Transaction>,
events: Option<Vec<Vec<ContractEvent>>>,
first_transaction_version: Option<Version>,
proof: TransactionInfoListWithProof,
) -> 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 {
pub transactions_and_outputs: Vec<(Transaction, TransactionOutput)>,
pub first_transaction_output_version: Option<Version>,
pub proof: TransactionInfoListWithProof,
}
impl TransactionOutputListWithProof {
pub fn new(
transactions_and_outputs: Vec<(Transaction, TransactionOutput)>,
first_transaction_output_version: Option<Version>,
proof: TransactionInfoListWithProof,
) -> Self {
Self {
transactions_and_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 and output version ({:?}) doesn't match given version ({:?}).",
self.first_transaction_output_version,
first_transaction_output_version,
);
ensure!(
self.proof.transaction_infos.len() == self.transactions_and_outputs.len(),
"The number of TransactionInfo objects ({}) does not match the number of \
transactions and outputs ({}).",
self.proof.transaction_infos.len(),
self.transactions_and_outputs.len(),
);
itertools::zip_eq(
&self.transactions_and_outputs,
&self.proof.transaction_infos,
)
.map(|((txn, txn_output), txn_info)| {
verify_events_against_root_hash(&txn_output.events, txn_info)?;
let write_set_hash = CryptoHash::hash(&txn_output.write_set);
ensure!(
txn_info.state_change_hash == write_set_hash,
"The write set in transaction output does not match the transaction info \
in proof. Hash of write set in transaction output: {}. Write set hash in txn_info: {}.",
write_set_hash,
txn_info.state_change_hash,
);
ensure!(
txn_output.gas_used() == txn_info.gas_used(),
"The gas used in transaction output does not match the transaction info \
in proof. Gas used in transaction output: {}. Gas used in txn_info: {}.",
txn_output.gas_used(),
txn_info.gas_used(),
);
ensure!(
*txn_output.status() == TransactionStatus::Keep(txn_info.status().clone()),
"The execution status of transaction output does not match the transaction \
info in proof. Status in transaction output: {:?}. Status in txn_info: {:?}.",
txn_output.status(),
txn_info.status(),
);
let txn_hash = txn.hash();
ensure!(
txn_hash == txn_info.transaction_hash(),
"The transaction hash does not match the hash in transaction info. \
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_output_version)?;
Ok(())
}
}
fn verify_events_against_root_hash(
events: &[ContractEvent],
transaction_info: &TransactionInfo,
) -> 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(pub Vec<TransactionWithProof>);
impl AccountTransactionsWithProof {
pub fn new(txns_with_proofs: Vec<TransactionWithProof>) -> 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] {
&self.0
}
pub fn into_inner(self) -> Vec<TransactionWithProof> {
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),
StateCheckpoint(HashValue),
}
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"),
Transaction::StateCheckpoint(_) => String::from("state_checkpoint"),
}
}
}
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.")),
}
}
}