nimbus-eth1/nimbus/transaction.nim

267 lines
7.5 KiB
Nim
Raw Normal View History

# 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.
import
./constants, ./errors, eth/[common, keys], ./utils,
stew/shims/macros,
./forks, ./vm_gas_costs
import eth/common/transaction as common_transaction
export common_transaction
2018-11-20 17:35:11 +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]
for i in data:
if i == 0:
2019-11-11 05:20:46 +00:00
result += gasFees[fork][GasTXDataZero]
else:
2019-11-11 05:20:46 +00:00
result += gasFees[fork][GasTXDataNonZero]
proc intrinsicGas*(tx: LegacyTx, fork: Fork): GasInt =
# 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)
if tx.isContractCreation:
2019-08-26 16:18:56 +00:00
result = result + gasFees[fork][GasTXCreate]
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]
proc intrinsicGas*(tx: Transaction, fork: Fork): GasInt =
txc(intrinsicGas, fork)
proc getSignature*(tx: LegacyTx, output: var Signature): bool =
var bytes: array[65, byte]
bytes[0..31] = tx.R.toByteArrayBE()
bytes[32..63] = tx.S.toByteArrayBE()
2019-04-18 08:33:17 +00:00
# TODO: V will become a byte or range soon.
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)
let sig = Signature.fromRaw(bytes)
if sig.isOk:
output = sig[]
return true
return false
2018-09-10 08:44:07 +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")
proc getSender*(tx: LegacyTx | AccessListTx | Transaction, output: var EthAddress): bool =
## Find the address the transaction was sent from.
2018-09-10 08:44:07 +00:00
var sig: Signature
if tx.getSignature(sig):
var txHash = tx.txHashNoSignature
let pubkey = recover(sig, SkMessage(txHash.data))
if pubkey.isOk:
output = pubkey[].toCanonicalAddress()
2018-09-10 08:44:07 +00:00
result = true
proc getSender*(tx: LegacyTx | AccessListTx | Transaction): EthAddress =
## Raises error on failure to recover public key
if not tx.getSender(result):
raise newException(ValidationError, "Could not derive sender address from transaction")
2018-09-10 08:44:07 +00:00
proc getRecipient*(tx: LegacyTx | AccessListTx, sender: EthAddress): EthAddress =
2019-02-26 07:04:12 +00:00
if tx.isContractCreation:
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
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)
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
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
vMin = 27'i64
vMax = 28'i64
2019-08-29 13:44:54 +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
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")
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)