Transactions

A transaction in Near is a list of actions and additional information:


#![allow(unused)]
fn main() {
pub struct Transaction {
    /// An account on which behalf transaction is signed
    pub signer_id: AccountId,
    /// An access key which was used to sign a transaction
    pub public_key: PublicKey,
    /// Nonce is used to determine order of transaction in the pool.
    /// It increments for a combination of `signer_id` and `public_key`
    pub nonce: Nonce,
    /// Receiver account for this transaction. If
    pub receiver_id: AccountId,
    /// The hash of the block in the blockchain on top of which the given transaction is valid
    pub block_hash: CryptoHash,
    /// A list of actions to be applied
    pub actions: Vec<Action>,
}
}

Signed Transaction

SignedTransaction is what the node receives from a wallet through JSON-RPC endpoint and then routed to the shard where receiver_id account lives. Signature proves an ownership of the corresponding public_key (which is an AccessKey for a particular account) as well as authenticity of the transaction itself.


#![allow(unused)]
fn main() {
pub struct SignedTransaction {
    pub transaction: Transaction,
    /// A signature of a hash of the Borsh-serialized Transaction
    pub signature: Signature,
}

Take a look some scenarios how transaction can be applied.

Batched Transaction

A Transaction can contain a list of actions. When there are more than one action in a transaction, we refer to such transaction as batched transaction. When such a transaction is applied, it is equivalent to applying each of the actions separately, except:

  • After processing a CreateAccount action, the rest of the action is applied on behalf of the account that is just created. This allows one to, in one transaction, create an account, deploy a contract to the account, and call some initialization function on the contract.
  • DeleteAccount action, if present, must be the last action in the transaction.

The number of actions in one transaction is limited by VMLimitConfig::max_actions_per_receipt, the current value of which is 100.

Transaction Validation and Errors

When a transaction is received, various checks will be performed to ensure its validity. This section lists the checks and potentially errors returned when they fail.

Basic Validation

Basic validation of a transaction can be done without the state. Such validation includes

  • Whether signer_id is valid. If not, a

#![allow(unused)]
fn main() {
/// TX signer_id is not in a valid format or not satisfy requirements see `near_core::primitives::utils::is_valid_account_id`
InvalidSignerId { signer_id: AccountId },
}

error is returned.

  • Whether receiver_id is valid. If not, a

#![allow(unused)]
fn main() {
/// TX receiver_id is not in a valid format or not satisfy requirements see `near_core::primitives::utils::is_valid_account_id`
InvalidReceiverId { receiver_id: AccountId },
}

error is returned.

  • Whether signature is signed by public_key. If not, a

#![allow(unused)]
fn main() {
/// TX signature is not valid
InvalidSignature
}

error is returned.

  • Whether the number of actions included in the transaction is no greater than max_actions_per_receipt. If not, a

#![allow(unused)]
fn main() {
 /// The number of actions exceeded the given limit.
TotalNumberOfActionsExceeded { total_number_of_actions: u64, limit: u64 }
}

is returned.

  • Among the actions in the transaction, whether DeleteAccount, if present, is the last action. If not, a

#![allow(unused)]
fn main() {
/// The delete action must be a final aciton in transaction
DeleteActionMustBeFinal
}

error is returned.

  • Whether total prepaid gas does not exceed max_total_prepaid_gas. If not, a

#![allow(unused)]
fn main() {
/// The total prepaid gas (for all given actions) exceeded the limit.
TotalPrepaidGasExceeded { total_prepaid_gas: Gas, limit: Gas }
}

error is returned.

  • Whether each action included is valid. Details of such check can be found in action.

Validation With State

After the basic validation is done, we check the transaction against current state to perform further validation. This includes

  • Whether signer_id exists. If not, a

#![allow(unused)]
fn main() {
/// TX signer_id is not found in a storage
SignerDoesNotExist { signer_id: AccountId },
}

error is returned.

  • Whether the transaction nonce is greater than the existing nonce on the access key. If not, a

#![allow(unused)]
fn main() {
/// Transaction nonce must be account[access_key].nonce + 1
InvalidNonce { tx_nonce: Nonce, ak_nonce: Nonce },
}

error is returned.

  • If signer_id account has enough balance to cover the cost of the transaction. If not, a

#![allow(unused)]
fn main() {
 /// Account does not have enough balance to cover TX cost
NotEnoughBalance {
    signer_id: AccountId,
    balance: Balance,
    cost: Balance,
}
}

error is returned.

  • If the transaction is signed by a function call access key and the function call access key does not have enough allowance to cover the cost of the transaction, a

#![allow(unused)]
fn main() {
/// Access Key does not have enough allowance to cover transaction cost
NotEnoughAllowance {
    account_id: AccountId,
    public_key: PublicKey,
    allowance: Balance,
    cost: Balance,
}
}

error is returned.

  • If signer_id account does not have enough balance to cover its storage after paying for the cost of the transaction, a

#![allow(unused)]
fn main() {
/// Signer account doesn't have enough balance after transaction.
LackBalanceForState {
    /// An account which doesn't have enough balance to cover storage.
    signer_id: AccountId,
    /// Required balance to cover the state.
    amount: Balance,
}
}

error is returned.

  • If a transaction is signed by a function call access key, the following errors are possible:
  • InvalidAccessKeyError::RequiresFullAccess if the transaction contains more than one action or if the only action it contains is not a FunctionCall action.
  • InvalidAccessKeyError::DepositWithFunctionCall if the function call action has nonzero deposit.

#![allow(unused)]
fn main() {
/// Transaction `receiver_id` doesn't match the access key receiver_id
InvalidAccessKeyError::ReceiverMismatch { tx_receiver: AccountId, ak_receiver: AccountId },
}

is returned when transaction's receiver_id does not match the receiver_id of the access key.


#![allow(unused)]
fn main() {
/// Transaction method name isn't allowed by the access key
InvalidAccessKeyError::MethodNameMismatch { method_name: String },
}

is returned if the name of the method that the transaction tries to call is not allowed by the access key.