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
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
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}
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
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
}
execute_transaction(EVM.state, t, Block.Header.t) :: {EVM.state, EVM.Gas.t, EVM.SubState.logs}
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>>}]
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},
]
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
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
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>>, "", ""]