2018-04-06 14:52:10 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2018 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
2018-01-17 12:57:50 +00:00
|
|
|
import
|
2021-03-31 10:14:02 +00:00
|
|
|
./constants, ./errors, eth/[common, keys], ./utils,
|
2021-05-15 06:37:40 +00:00
|
|
|
stew/shims/macros,
|
2021-06-01 10:54:13 +00:00
|
|
|
./forks, ./vm_gas_costs
|
2018-01-17 12:57:50 +00:00
|
|
|
|
2019-08-13 08:13:22 +00:00
|
|
|
import eth/common/transaction as common_transaction
|
|
|
|
export common_transaction
|
2018-11-20 17:35:11 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
template txc(fn: untyped, params: varargs[untyped]): untyped =
|
|
|
|
if tx.txType == LegacyTxType:
|
|
|
|
unpackArgs(fn, [tx.legacyTx, params])
|
|
|
|
else:
|
|
|
|
unpackArgs(fn, [tx.accessListTx, params])
|
|
|
|
|
|
|
|
template txField(field: untyped): untyped =
|
|
|
|
if tx.txType == LegacyTxType:
|
|
|
|
tx.legacyTx.field
|
|
|
|
else:
|
|
|
|
tx.accessListTx.field
|
|
|
|
|
|
|
|
template txFieldAsgn(field, data: untyped) =
|
|
|
|
if tx.txType == LegacyTxType:
|
|
|
|
tx.legacyTx.field = data
|
|
|
|
else:
|
|
|
|
tx.accessListTx.field = data
|
|
|
|
|
2021-05-15 07:18:21 +00:00
|
|
|
template recField(field: untyped): untyped =
|
|
|
|
if rec.receiptType == LegacyReceiptType:
|
|
|
|
rec.legacyReceipt.field
|
|
|
|
else:
|
|
|
|
rec.accessListReceipt.field
|
|
|
|
|
2019-11-11 05:20:46 +00:00
|
|
|
func intrinsicGas*(data: openarray[byte], fork: Fork): GasInt =
|
|
|
|
result = gasFees[fork][GasTransaction]
|
2018-12-03 19:47:20 +00:00
|
|
|
for i in data:
|
|
|
|
if i == 0:
|
2019-11-11 05:20:46 +00:00
|
|
|
result += gasFees[fork][GasTXDataZero]
|
2018-12-03 19:47:20 +00:00
|
|
|
else:
|
2019-11-11 05:20:46 +00:00
|
|
|
result += gasFees[fork][GasTXDataNonZero]
|
2018-12-03 19:47:20 +00:00
|
|
|
|
2021-05-15 13:54:34 +00:00
|
|
|
proc intrinsicGas*(tx: LegacyTx, fork: Fork): GasInt =
|
2018-01-17 12:57:50 +00:00
|
|
|
# Compute the baseline gas cost for this transaction. This is the amount
|
|
|
|
# of gas needed to send this transaction (but that is not actually used
|
|
|
|
# for computation)
|
2019-11-11 05:20:46 +00:00
|
|
|
result = tx.payload.intrinsicGas(fork)
|
2018-01-17 12:57:50 +00:00
|
|
|
|
2019-08-26 15:19:18 +00:00
|
|
|
if tx.isContractCreation:
|
2019-08-26 16:18:56 +00:00
|
|
|
result = result + gasFees[fork][GasTXCreate]
|
2019-08-26 15:19:18 +00:00
|
|
|
|
2021-05-15 13:54:34 +00:00
|
|
|
proc intrinsicGas*(tx: AccessListTx, fork: Fork): GasInt =
|
|
|
|
const
|
|
|
|
ADDRESS_COST = 2400
|
|
|
|
STORAGE_KEY_COST = 1900
|
|
|
|
|
|
|
|
# Compute the baseline gas cost for this transaction. This is the amount
|
|
|
|
# of gas needed to send this transaction (but that is not actually used
|
|
|
|
# for computation)
|
|
|
|
result = tx.payload.intrinsicGas(fork)
|
|
|
|
|
|
|
|
result = result + tx.accessList.len * ADDRESS_COST
|
|
|
|
var numKeys = 0
|
|
|
|
for n in tx.accessList:
|
|
|
|
inc(numKeys, n.storageKeys.len)
|
|
|
|
|
|
|
|
result = result + numKeys * STORAGE_KEY_COST
|
|
|
|
if tx.isContractCreation:
|
|
|
|
result = result + gasFees[fork][GasTXCreate]
|
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc intrinsicGas*(tx: Transaction, fork: Fork): GasInt =
|
|
|
|
txc(intrinsicGas, fork)
|
|
|
|
|
|
|
|
proc getSignature*(tx: LegacyTx, output: var Signature): bool =
|
2018-08-24 15:46:48 +00:00
|
|
|
var bytes: array[65, byte]
|
2021-05-15 06:37:40 +00:00
|
|
|
bytes[0..31] = tx.R.toByteArrayBE()
|
|
|
|
bytes[32..63] = tx.S.toByteArrayBE()
|
2019-04-18 08:33:17 +00:00
|
|
|
|
2018-08-24 15:46:48 +00:00
|
|
|
# TODO: V will become a byte or range soon.
|
2021-05-15 06:37:40 +00:00
|
|
|
var v = tx.V
|
2019-04-18 08:33:17 +00:00
|
|
|
if v >= EIP155_CHAIN_ID_OFFSET:
|
|
|
|
v = 28 - (v and 0x01)
|
2018-09-10 08:44:07 +00:00
|
|
|
elif v == 27 or v == 28:
|
2019-04-18 08:33:17 +00:00
|
|
|
discard
|
2018-09-10 08:44:07 +00:00
|
|
|
else:
|
|
|
|
return false
|
|
|
|
|
2019-04-18 08:33:17 +00:00
|
|
|
bytes[64] = byte(v - 27)
|
2020-04-05 13:12:48 +00:00
|
|
|
let sig = Signature.fromRaw(bytes)
|
|
|
|
if sig.isOk:
|
|
|
|
output = sig[]
|
|
|
|
return true
|
|
|
|
return false
|
2018-09-10 08:44:07 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc getSignature*(tx: AccessListTx, output: var Signature): bool =
|
|
|
|
var bytes: array[65, byte]
|
|
|
|
bytes[0..31] = tx.R.toByteArrayBE()
|
|
|
|
bytes[32..63] = tx.S.toByteArrayBE()
|
|
|
|
bytes[64] = tx.V.byte
|
|
|
|
let sig = Signature.fromRaw(bytes)
|
|
|
|
if sig.isOk:
|
|
|
|
output = sig[]
|
|
|
|
return true
|
|
|
|
return false
|
|
|
|
|
|
|
|
proc getSignature*(tx: Transaction, output: var Signature): bool =
|
|
|
|
txc(getSignature, output)
|
|
|
|
|
|
|
|
proc toSignature*(tx: Transaction): Signature =
|
|
|
|
if not getSignature(tx, result):
|
2019-08-29 12:57:01 +00:00
|
|
|
raise newException(Exception, "Invalid signature")
|
2018-08-24 15:46:48 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc getSender*(tx: LegacyTx | AccessListTx | Transaction, output: var EthAddress): bool =
|
2018-08-24 15:46:48 +00:00
|
|
|
## Find the address the transaction was sent from.
|
2018-09-10 08:44:07 +00:00
|
|
|
var sig: Signature
|
2021-05-15 06:37:40 +00:00
|
|
|
if tx.getSignature(sig):
|
|
|
|
var txHash = tx.txHashNoSignature
|
2020-07-20 06:50:05 +00:00
|
|
|
let pubkey = recover(sig, SkMessage(txHash.data))
|
2020-04-05 13:12:48 +00:00
|
|
|
if pubkey.isOk:
|
|
|
|
output = pubkey[].toCanonicalAddress()
|
2018-09-10 08:44:07 +00:00
|
|
|
result = true
|
2018-08-24 15:46:48 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc getSender*(tx: LegacyTx | AccessListTx | Transaction): EthAddress =
|
2018-08-29 15:52:12 +00:00
|
|
|
## Raises error on failure to recover public key
|
2021-05-15 06:37:40 +00:00
|
|
|
if not tx.getSender(result):
|
2018-08-29 15:52:12 +00:00
|
|
|
raise newException(ValidationError, "Could not derive sender address from transaction")
|
2018-09-10 08:44:07 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc getRecipient*(tx: LegacyTx | AccessListTx, sender: EthAddress): EthAddress =
|
2019-02-26 07:04:12 +00:00
|
|
|
if tx.isContractCreation:
|
2021-05-15 06:37:40 +00:00
|
|
|
result = generateAddress(sender, tx.nonce)
|
2019-02-26 07:04:12 +00:00
|
|
|
else:
|
|
|
|
result = tx.to
|
2019-08-29 12:57:01 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc getRecipient*(tx: Transaction, sender: EthAddress): EthAddress =
|
|
|
|
txc(getRecipient, sender)
|
|
|
|
|
|
|
|
proc gasLimit*(tx: Transaction): GasInt =
|
|
|
|
txField(gasLimit)
|
|
|
|
|
|
|
|
proc gasPrice*(tx: Transaction): GasInt =
|
|
|
|
txField(gasPrice)
|
|
|
|
|
|
|
|
proc value*(tx: Transaction): UInt256 =
|
|
|
|
txField(value)
|
|
|
|
|
|
|
|
proc isContractCreation*(tx: Transaction): bool =
|
|
|
|
txField(isContractCreation)
|
|
|
|
|
|
|
|
proc to*(tx: Transaction): EthAddress =
|
|
|
|
txField(to)
|
|
|
|
|
|
|
|
proc payload*(tx: Transaction): Blob =
|
|
|
|
txField(payload)
|
|
|
|
|
|
|
|
proc nonce*(tx: Transaction): AccountNonce =
|
|
|
|
txField(nonce)
|
|
|
|
|
2021-05-15 07:18:21 +00:00
|
|
|
proc V*(tx: Transaction): int64 =
|
|
|
|
txField(V)
|
|
|
|
|
|
|
|
proc R*(tx: Transaction): UInt256 =
|
|
|
|
txField(R)
|
|
|
|
|
|
|
|
proc S*(tx: Transaction): UInt256 =
|
|
|
|
txField(S)
|
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc `payload=`*(tx: var Transaction, data: Blob) =
|
|
|
|
txFieldAsgn(payload, data)
|
|
|
|
|
|
|
|
proc `gasLimit=`*(tx: var Transaction, data: GasInt) =
|
|
|
|
txFieldAsgn(gasLimit, data)
|
|
|
|
|
2021-05-15 07:18:21 +00:00
|
|
|
proc cumulativeGasUsed*(rec: Receipt): GasInt =
|
|
|
|
recField(cumulativeGasUsed)
|
|
|
|
|
|
|
|
proc logs*(rec: Receipt): auto =
|
|
|
|
recField(logs)
|
|
|
|
|
|
|
|
proc bloom*(rec: Receipt): auto =
|
|
|
|
recField(bloom)
|
|
|
|
|
|
|
|
proc hasStateRoot*(rec: Receipt): bool =
|
|
|
|
if rec.receiptType == LegacyReceiptType:
|
|
|
|
rec.legacyReceipt.hasStateRoot
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
|
|
|
|
proc hasStatus*(rec: Receipt): bool =
|
|
|
|
if rec.receiptType == LegacyReceiptType:
|
|
|
|
rec.legacyReceipt.hasStatus
|
|
|
|
else:
|
|
|
|
true
|
|
|
|
|
|
|
|
proc status*(rec: Receipt): int =
|
|
|
|
if rec.receiptType == LegacyReceiptType:
|
|
|
|
rec.legacyReceipt.status
|
|
|
|
else:
|
|
|
|
rec.accessListReceipt.status.int
|
|
|
|
|
|
|
|
proc stateRoot*(rec: Receipt): Hash256 =
|
|
|
|
if rec.receiptType == LegacyReceiptType:
|
|
|
|
return rec.legacyReceipt.stateRoot
|
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
proc validate*(tx: LegacyTx, fork: Fork) =
|
2019-08-29 12:57:01 +00:00
|
|
|
# Hook called during instantiation to ensure that all transaction
|
|
|
|
# parameters pass validation rules
|
|
|
|
if tx.intrinsicGas(fork) > tx.gasLimit:
|
|
|
|
raise newException(ValidationError, "Insufficient gas")
|
|
|
|
|
|
|
|
# check signature validity
|
|
|
|
var sender: EthAddress
|
|
|
|
if not tx.getSender(sender):
|
|
|
|
raise newException(ValidationError, "Invalid signature or failed message verification")
|
|
|
|
|
2019-08-29 13:44:54 +00:00
|
|
|
var
|
2021-05-15 06:37:40 +00:00
|
|
|
vMin = 27'i64
|
|
|
|
vMax = 28'i64
|
2019-08-29 13:44:54 +00:00
|
|
|
|
2021-05-15 06:37:40 +00:00
|
|
|
if tx.V >= EIP155_CHAIN_ID_OFFSET:
|
|
|
|
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
|
2019-08-29 13:44:54 +00:00
|
|
|
vMin = 35 + (2 * chainId)
|
|
|
|
vMax = vMin + 1
|
|
|
|
|
|
|
|
var isValid = tx.R >= Uint256.one
|
|
|
|
isValid = isValid and tx.S >= Uint256.one
|
2021-05-15 06:37:40 +00:00
|
|
|
isValid = isValid and tx.V >= vMin
|
|
|
|
isValid = isValid and tx.V <= vMax
|
2019-08-29 13:44:54 +00:00
|
|
|
isValid = isValid and tx.S < SECPK1_N
|
|
|
|
isValid = isValid and tx.R < SECPK1_N
|
|
|
|
|
|
|
|
if fork >= FkHomestead:
|
|
|
|
isValid = isValid and tx.S < SECPK1_N div 2
|
|
|
|
|
|
|
|
if not isValid:
|
|
|
|
raise newException(ValidationError, "Invalid transaction")
|
2021-05-15 07:30:39 +00:00
|
|
|
|
|
|
|
proc validate*(tx: AccessListTx, fork: Fork) =
|
|
|
|
if tx.intrinsicGas(fork) > tx.gasLimit:
|
|
|
|
raise newException(ValidationError, "Insufficient gas")
|
|
|
|
|
|
|
|
# check signature validity
|
|
|
|
var sender: EthAddress
|
|
|
|
if not tx.getSender(sender):
|
|
|
|
raise newException(ValidationError, "Invalid signature or failed message verification")
|
|
|
|
|
|
|
|
var isValid = tx.V in {0'i64, 1'i64}
|
|
|
|
isValid = isValid and tx.S >= Uint256.one
|
|
|
|
isValid = isValid and tx.S < SECPK1_N
|
|
|
|
isValid = isValid and tx.R < SECPK1_N
|
|
|
|
|
|
|
|
# TODO: chainId need validation?
|
|
|
|
# TODO: accessList need validation?
|
|
|
|
|
|
|
|
if not isValid:
|
|
|
|
raise newException(ValidationError, "Invalid transaction")
|
|
|
|
|
|
|
|
proc validate*(tx: Transaction, fork: Fork) =
|
|
|
|
if tx.txType == LegacyTxType:
|
|
|
|
validate(tx.legacyTx, fork)
|
|
|
|
else:
|
|
|
|
validate(tx.accessListTx, fork)
|