blockchain v0.1.5 Blockchain.Transaction

This module encodes the transaction object, defined in Section 4.3 of the Yellow Paper (http://gavwood.com/Paper.pdf). We are focused on implementing 𝛶, as defined in Eq.(1).

Link to this section Summary

Functions

Performs first step of transaction, which adjusts the sender’s balance and nonce, as defined in Eq.(67), Eq.(68) and Eq.(69) of the Yellow Paper

Caluclates the amount which should be refunded based on the current transactions final usage. This includes the remaining gas plus refunds from clearing storage

Decodes a transaction that was previously encoded using Transaction.serialize/1. Note, this is the inverse of L_T Eq.(14) defined in the Yellow Paper

Performs transaction execution, as defined in Section 6 of the Yellow Paper, defined there as 𝛶, Eq.(1) and Eq.(59), Eq.(70), Eq.(79) and Eq.(80)

Finalizes the gas payout, repaying the sender for excess or refunded gas and paying the miner his due. This is defined according to Eq.(73), Eq.(74), Eq.(75) and Eq.(76) of the Yellow Paper

Defines the “intrinsic gas cost,” that is the amount of gas this transaction requires to be paid prior to execution. This is defined as g_0 in Eq.(62), Eq.(63) and Eq.(64) of the Yellow Paper

Validates the validity of a transaction that is required to be true before we’re willing to execute a transaction. This is specified in Section 6.2 of the Yellow Paper Eq.(65) and Eq.(66)

Encodes a transaction such that it can be RLP-encoded. This is defined at L_T Eq.(14) in the Yellow Paper

Link to this section Types

Link to this type t()
t() :: %Blockchain.Transaction{data: binary, gas_limit: EVM.val, gas_price: EVM.val, init: EVM.MachineCode.t, nonce: EVM.val, r: Blockchain.Transaction.Signature.hash_r, s: Blockchain.Transaction.Signature.hash_s, to: EVM.address | <<_::0>>, v: Blockchain.Transaction.Signature.hash_v, value: EVM.val}

Link to this section Functions

Link to this function begin_transaction(state, sender, trx)
begin_transaction(EVM.state, EVM.address, t) :: EVM.state

Performs first step of transaction, which adjusts the sender’s balance and nonce, as defined in Eq.(67), Eq.(68) and Eq.(69) of the Yellow Paper.

Note: we pass in sender here so we do not need to compute it

  several times (since we'll use it elsewhere).

TODO: we execute this as two separate updates; we may want to

  combine a series of updates before we update our state.

Examples

iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 1000, nonce: 7})
iex> state = Blockchain.Transaction.begin_transaction(state, <<0x01::160>>, %Blockchain.Transaction{gas_price: 3, gas_limit: 100})
iex> Blockchain.Account.get_account(state, <<0x01::160>>)
%Blockchain.Account{balance: 700, nonce: 8}
Link to this function calculate_total_refund(trx, remaining_gas, refund)
calculate_total_refund(t, EVM.Gas.t, EVM.SubState.refund) :: EVM.Gas.t

Caluclates the amount which should be refunded based on the current transactions final usage. This includes the remaining gas plus refunds from clearing storage.

The specs calls for capping the refund at half of the total amount of gas used.

This function is defined as g* in Eq.(72) in the Yellow Paper.

Examples

iex> Blockchain.Transaction.calculate_total_refund(%Blockchain.Transaction{gas_limit: 100}, 10, 5)
15

iex> Blockchain.Transaction.calculate_total_refund(%Blockchain.Transaction{gas_limit: 100}, 10, 99)
55

iex> Blockchain.Transaction.calculate_total_refund(%Blockchain.Transaction{gas_limit: 100}, 10, 0)
10

iex> Blockchain.Transaction.calculate_total_refund(%Blockchain.Transaction{gas_limit: 100}, 11, 99)
55
Link to this function deserialize(rlp)
deserialize(ExRLP.t) :: t

Decodes a transaction that was previously encoded using Transaction.serialize/1. Note, this is the inverse of L_T Eq.(14) defined in the Yellow Paper.

Examples

iex> Blockchain.Transaction.deserialize([<<5>>, <<6>>, <<7>>, <<1::160>>, <<8>>, "hi", <<27>>, <<9>>, <<10>>])
%Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1::160>>, value: 8, v: 27, r: 9, s: 10, data: "hi"}

iex> Blockchain.Transaction.deserialize([<<5>>, <<6>>, <<7>>, <<>>, <<8>>, <<1, 2, 3>>, <<27>>, <<9>>, <<10>>])
%Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 8, v: 27, r: 9, s: 10, init: <<1, 2, 3>>}

iex> Blockchain.Transaction.deserialize(["  ", <<4, 168, 23, 200, 0>>, "R", "55555555555555555555", <<13, 224, 182, 179, 167, 100, 0, 0>>, "", <<1>>, "", ""])
%Blockchain.Transaction{
  data: "",
  gas_limit: 21000,
  gas_price: 20000000000,
  init: "",
  nonce: 9,
  r: 0,
  s: 0,
  to: "55555555555555555555",
  v: 1,
  value: 1000000000000000000
}
Link to this function execute_transaction(state, trx, block_header)

Performs transaction execution, as defined in Section 6 of the Yellow Paper, defined there as 𝛶, Eq.(1) and Eq.(59), Eq.(70), Eq.(79) and Eq.(80).

From the Yellow Paper, T_o is the original transactor, which can differ from the sender in the case of a message call or contract creation not directly triggered by a transaction but coming from the execution of EVM-code.

TODO: Add rich examples in transaction_test.exs

Examples

# Create contract
iex> beneficiary = <<0x05::160>>
iex> private_key = <<1::256>>
iex> sender = <<126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223>> # based on simple private key
iex> contract_address = Blockchain.Contract.new_contract_address(sender, 6)
iex> machine_code = EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 32, :push1, 0, :return])
iex> trx = %Blockchain.Transaction{nonce: 5, gas_price: 3, gas_limit: 100_000, to: <<>>, value: 5, init: machine_code}
...>       |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> {state, gas, logs} = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(sender, %Blockchain.Account{balance: 400_000, nonce: 5})
...> |> Blockchain.Transaction.execute_transaction(trx, %Block.Header{beneficiary: beneficiary})
iex> {gas, logs}
{53780, <<>>}
iex> Blockchain.Account.get_accounts(state, [sender, beneficiary, contract_address])
[%Blockchain.Account{balance: 238655, nonce: 6}, %Blockchain.Account{balance: 161340}, %Blockchain.Account{balance: 5, code_hash: <<243, 247, 169, 254, 54, 79, 170, 185, 59, 33, 109, 165, 10, 50, 20, 21, 79, 34, 160, 162, 180, 21, 178, 58, 132, 200, 22, 158, 139, 99, 110, 227>>}]

# Message call
iex> beneficiary = <<0x05::160>>
iex> private_key = <<1::256>>
iex> sender = <<126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223>> # based on simple private key
iex> contract_address = Blockchain.Contract.new_contract_address(sender, 6)
iex> machine_code = EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 0, :push1, 32, :return])
iex> trx = %Blockchain.Transaction{nonce: 5, gas_price: 3, gas_limit: 100_000, to: contract_address, value: 5, init: machine_code}
...>       |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> {state, gas, logs} = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(sender, %Blockchain.Account{balance: 400_000, nonce: 5})
...> |> Blockchain.Account.put_code(contract_address, machine_code)
...> |> Blockchain.Transaction.execute_transaction(trx, %Block.Header{beneficiary: beneficiary})
iex> {gas, logs}
{21780, <<>>}
iex> Blockchain.Account.get_accounts(state, [sender, beneficiary, contract_address])
[%Blockchain.Account{balance: 334655, nonce: 6}, %Blockchain.Account{balance: 65340}, %Blockchain.Account{balance: 5, code_hash: <<216, 114, 80, 103, 17, 50, 164, 75, 162, 123, 123, 99, 162, 105, 226, 15, 215, 200, 136, 216, 29, 106, 193, 119, 1, 173, 138, 37, 219, 39, 23, 231>>}]
Link to this function finalize_transaction_gas(state, sender, trx, total_refund, block_header)
finalize_transaction_gas(EVM.state, EVM.address, t, EVM.Gas.t, Block.Header.t) :: EVM.state

Finalizes the gas payout, repaying the sender for excess or refunded gas and paying the miner his due. This is defined according to Eq.(73), Eq.(74), Eq.(75) and Eq.(76) of the Yellow Paper.

Again, we take a sender so that we don’t have to re-compute the sender address several times.

Examples

iex> trx = %Blockchain.Transaction{gas_price: 10, gas_limit: 30}
iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...>   |> Blockchain.Account.put_account(<<0x01::160>>, %Blockchain.Account{balance: 11})
...>   |> Blockchain.Account.put_account(<<0x02::160>>, %Blockchain.Account{balance: 22})
iex> Blockchain.Transaction.finalize_transaction_gas(state, <<0x01::160>>, trx, 5, %Block.Header{beneficiary: <<0x02::160>>})
...>   |> Blockchain.Account.get_accounts([<<0x01::160>>, <<0x02::160>>])
[
  %Blockchain.Account{balance: 61},
  %Blockchain.Account{balance: 272},
]
Link to this function intrinsic_gas_cost(trx, block_header)
intrinsic_gas_cost(t, Block.Header.t) :: EVM.Gas.t

Defines the “intrinsic gas cost,” that is the amount of gas this transaction requires to be paid prior to execution. This is defined as g_0 in Eq.(62), Eq.(63) and Eq.(64) of the Yellow Paper.

Examples

iex> Blockchain.Transaction.intrinsic_gas_cost(%Blockchain.Transaction{to: <<1::160>>, init: <<>>, data: <<1, 2, 0, 3>>}, %Block.Header{number: 5})
3 * 68 + 4 + 21000

iex> Blockchain.Transaction.intrinsic_gas_cost(%Blockchain.Transaction{to: <<1::160>>, init: <<>>, data: <<1, 2, 0, 3>>}, %Block.Header{number: 5_000_000})
3 * 68 + 4 + 21000

iex> Blockchain.Transaction.intrinsic_gas_cost(%Blockchain.Transaction{to: <<1::160>>, init: <<>>, data: <<>>}, %Block.Header{number: 5_000_000})
21000

iex> Blockchain.Transaction.intrinsic_gas_cost(%Blockchain.Transaction{to: <<>>, init: <<1, 2, 0, 3>>, data: <<>>}, %Block.Header{number: 5})
3 * 68 + 4 + 21000

iex> Blockchain.Transaction.intrinsic_gas_cost(%Blockchain.Transaction{to: <<>>, init: <<1, 2, 0, 3>>, data: <<>>}, %Block.Header{number: 5_000_000})
3 * 68 + 4 + 32000 + 21000
Link to this function is_valid?(state, trx, block_header)
is_valid?(EVM.state, t, Block.Header.t) ::
  :valid |
  {:invalid, atom}

Validates the validity of a transaction that is required to be true before we’re willing to execute a transaction. This is specified in Section 6.2 of the Yellow Paper Eq.(65) and Eq.(66).

TODO: Consider returning a set of reasons, instead of a singular reason.

Examples

# Sender address is nil
iex> trx = %Blockchain.Transaction{data: <<>>, gas_limit: 1_000, gas_price: 1, init: <<1>>, nonce: 5, to: <<>>, value: 5, r: 1, s: 2, v: 3}
iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Transaction.is_valid?(trx, %Block.Header{})
{:invalid, :invalid_sender}

# Sender account is nil
iex> private_key = <<1::256>>
iex> trx =
...>   %Blockchain.Transaction{data: <<>>, gas_limit: 1_000, gas_price: 1, init: <<1>>, nonce: 5, to: <<>>, value: 5}
...>   |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Transaction.is_valid?(trx, %Block.Header{})
{:invalid, :missing_account}

# Has sender account, but nonce mismatch
iex> private_key = <<1::256>>
iex> sender = <<126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223>> # based on simple private key
iex> trx =
...>   %Blockchain.Transaction{data: <<>>, gas_limit: 1_000, gas_price: 1, init: <<1>>, nonce: 4, to: <<>>, value: 5}
...>   |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(sender, %Blockchain.Account{balance: 1000, nonce: 5})
...> |> Blockchain.Transaction.is_valid?(trx, %Block.Header{})
{:invalid, :nonce_mismatch}

# Insufficient starting gas
iex> private_key = <<1::256>>
iex> sender = <<126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223>> # based on simple private key
iex> trx =
...>   %Blockchain.Transaction{data: <<>>, gas_limit: 1_000, gas_price: 1, init: <<1>>, nonce: 5, to: <<>>, value: 5}
...>   |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(sender, %Blockchain.Account{balance: 1000, nonce: 5})
...> |> Blockchain.Transaction.is_valid?(trx, %Block.Header{})
{:invalid, :insufficient_intrinsic_gas}

# Insufficient endowment
iex> private_key = <<1::256>>
iex> sender = <<126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223>> # based on simple private key
iex> trx =
...>   %Blockchain.Transaction{data: <<>>, gas_limit: 100_000, gas_price: 1, init: <<1>>, nonce: 5, to: <<>>, value: 5}
...>   |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(sender, %Blockchain.Account{balance: 1000, nonce: 5})
...> |> Blockchain.Transaction.is_valid?(trx, %Block.Header{})
{:invalid, :insufficient_balance}

iex> private_key = <<1::256>>
iex> sender = <<126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223>> # based on simple private key
iex> trx =
...>   %Blockchain.Transaction{data: <<>>, gas_limit: 100_000, gas_price: 1, init: <<1>>, nonce: 5, to: <<>>, value: 5}
...>   |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(sender, %Blockchain.Account{balance: 100_001, nonce: 5})
...> |> Blockchain.Transaction.is_valid?(trx, %Block.Header{})
{:invalid, :insufficient_balance}

iex> private_key = <<1::256>>
iex> sender = <<126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223>> # based on simple private key
iex> trx =
...>   %Blockchain.Transaction{data: <<>>, gas_limit: 100_000, gas_price: 1, init: <<1>>, nonce: 5, to: <<>>, value: 5}
...>   |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(sender, %Blockchain.Account{balance: 100_006, nonce: 5})
...> |> Blockchain.Transaction.is_valid?(trx, %Block.Header{gas_limit: 50_000, gas_used: 49_999})
{:invalid, :over_gas_limit}

iex> private_key = <<1::256>>
iex> sender = <<126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223>> # based on simple private key
iex> trx =
...>   %Blockchain.Transaction{data: <<>>, gas_limit: 100_000, gas_price: 1, init: <<1>>, nonce: 5, to: <<>>, value: 5}
...>   |> Blockchain.Transaction.Signature.sign_transaction(private_key)
iex> MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db())
...> |> Blockchain.Account.put_account(sender, %Blockchain.Account{balance: 100_006, nonce: 5})
...> |> Blockchain.Transaction.is_valid?(trx, %Block.Header{gas_limit: 500_000, gas_used: 49_999})
:valid
Link to this function serialize(trx, include_vrs \\ true)

Encodes a transaction such that it can be RLP-encoded. This is defined at L_T Eq.(14) in the Yellow Paper.

Examples

iex> Blockchain.Transaction.serialize(%Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1::160>>, value: 8, v: 27, r: 9, s: 10, data: "hi"})
[<<5>>, <<6>>, <<7>>, <<1::160>>, <<8>>, "hi", <<27>>, <<9>>, <<10>>]

iex> Blockchain.Transaction.serialize(%Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 8, v: 27, r: 9, s: 10, init: <<1, 2, 3>>})
[<<5>>, <<6>>, <<7>>, <<>>, <<8>>, <<1, 2, 3>>, <<27>>, <<9>>, <<10>>]

iex> Blockchain.Transaction.serialize(%Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 8, v: 27, r: 9, s: 10, init: <<1, 2, 3>>}, false)
[<<5>>, <<6>>, <<7>>, <<>>, <<8>>, <<1, 2, 3>>]

iex> Blockchain.Transaction.serialize(%Blockchain.Transaction{ data: "", gas_limit: 21000, gas_price: 20000000000, init: "", nonce: 9, r: 0, s: 0, to: "55555555555555555555", v: 1, value: 1000000000000000000 })
["  ", <<4, 168, 23, 200, 0>>, "R", "55555555555555555555", <<13, 224, 182, 179, 167, 100, 0, 0>>, "", <<1>>, "", ""]