Use eth/common transaction signature utilities (#2696)

* Use eth/common transaction signature utilities

* bump

* bump

* bump

* bump

* bump

* bump
This commit is contained in:
Jacek Sieka 2024-10-04 16:34:31 +02:00 committed by GitHub
parent c42ae8a037
commit 08ffb3161c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 130 additions and 395 deletions

View File

@ -14,14 +14,14 @@ import
web3/conversions, # sigh, for FixedBytes marshalling
web3/eth_api_types,
web3/primitives as web3types,
eth/common/eth_types,
eth/common/[eth_types, transaction_utils],
beacon_chain/spec/forks,
../network/history/[history_network, history_content],
../network/state/[state_network, state_content, state_endpoints],
../network/beacon/beacon_light_client,
../version
from ../../nimbus/transaction import getSender, ValidationError
from ../../nimbus/errors import ValidationError
from ../../nimbus/rpc/filters import headerBloomFilter, deriveLogs, filterLogs
from ../../nimbus/beacon/web3_eth_conv import w3Addr, w3Hash, ethHash
@ -42,10 +42,13 @@ func init*(
header: eth_types.BlockHeader,
txIndex: int,
): T {.raises: [ValidationError].} =
let sender = tx.recoverSender().valueOr:
raise (ref ValidationError)(msg: "Invalid tx signature")
TransactionObject(
blockHash: Opt.some(w3Hash header.blockHash),
blockNumber: Opt.some(eth_api_types.BlockNumber(header.number)),
`from`: w3Addr tx.getSender(),
`from`: sender,
gas: Quantity(tx.gasLimit),
gasPrice: Quantity(tx.gasPrice),
hash: w3Hash tx.rlpHash,
@ -107,7 +110,6 @@ func init*(
if fullTx:
var i = 0
for tx in body.transactions:
# ValidationError from tx.getSender in TransactionObject.init
blockObject.transactions.add txOrHash(TransactionObject.init(tx, header, i))
inc i
else:

View File

@ -11,6 +11,7 @@
import
std/[tables],
eth/keys,
eth/common/transaction_utils,
stew/endians2,
nimcrypto/sha2,
chronicles,
@ -150,7 +151,8 @@ proc makeTxOfType(params: MakeTxParams, tc: BaseTx): PooledTransaction =
value : tc.amount,
gasLimit: tc.gasLimit,
gasPrice: gasPrice,
payload : tc.payload
payload : tc.payload,
chainId : params.chainId,
)
)
of TxEip1559:
@ -207,7 +209,7 @@ proc makeTx(params: MakeTxParams, tc: BaseTx): PooledTransaction =
# Build the transaction depending on the specified type
let tx = makeTxOfType(params, tc)
PooledTransaction(
tx: signTransaction(tx.tx, params.key, params.chainId, eip155 = true),
tx: signTransaction(tx.tx, params.key),
networkPayload: tx.networkPayload)
proc makeTx(params: MakeTxParams, tc: BigInitcodeTx): PooledTransaction =
@ -337,7 +339,7 @@ proc makeTx*(params: MakeTxParams, tc: BlobTx): PooledTransaction =
)
PooledTransaction(
tx: signTransaction(unsignedTx, params.key, params.chainId, eip155 = true),
tx: signTransaction(unsignedTx, params.key),
networkPayload: NetworkPayload(
blobs : data.blobs.mapIt(it.bytes),
commitments: data.commitments.mapIt(it.bytes),
@ -427,16 +429,10 @@ proc customizeTransaction*(sender: TxSender,
if custTx.data.isSome:
modTx.payload = custTx.data.get
if custTx.signature.isSome:
let signature = custTx.signature.get
modTx.V = signature.V
modTx.R = signature.R
modTx.S = signature.S
if custTx.chainId.isSome:
modTx.chainId = custTx.chainId.get
if baseTx.txType in {TxEip1559, TxEip4844}:
if custTx.chainId.isSome:
modTx.chainId = custTx.chainId.get
if custTx.gasPriceOrGasFeeCap.isSome:
modTx.maxFeePErGas = custTx.gasPriceOrGasFeeCap.get.GasInt
@ -448,7 +444,12 @@ proc customizeTransaction*(sender: TxSender,
var address: EthAddress
modTx.to = Opt.some(address)
if custTx.signature.isNone:
return signTransaction(modTx, acc.key, modTx.chainId, eip155 = true)
if custTx.signature.isSome:
let signature = custTx.signature.get
modTx.V = signature.V
modTx.R = signature.R
modTx.S = signature.S
else:
modTx.signature = modTx.sign(acc.key, eip155 = true)
return modTx
modTx

View File

@ -94,7 +94,7 @@ proc makeFundingTx*(
)
PooledTransaction(
tx: signTransaction(unsignedTx, v.vaultKey, v.chainId, eip155 = true))
tx: signTransaction(unsignedTx, v.vaultKey))
proc signTx*(v: Vault,
sender: EthAddress,
@ -118,7 +118,7 @@ proc signTx*(v: Vault,
let key = v.accounts[sender]
PooledTransaction(
tx: signTransaction(unsignedTx, key, v.chainId, eip155 = true))
tx: signTransaction(unsignedTx, key))
# createAccount creates a new account that is funded from the vault contract.
# It will panic when the account could not be created and funded.

View File

@ -21,6 +21,7 @@ import
./calculate_reward,
./executor_helpers,
./process_transaction,
eth/common/transaction_utils,
chronicles,
results
@ -40,8 +41,7 @@ proc processTransactions*(
vmState.allLogs = @[]
for txIndex, tx in transactions:
var sender: EthAddress
if not tx.getSender(sender):
let sender = tx.recoverSender().valueOr:
return err("Could not get sender for tx with index " & $(txIndex))
let rc = vmState.processTransaction(tx, sender, header)
if rc.isErr:

View File

@ -66,7 +66,7 @@ proc commitOrRollbackDependingOnGasUsed(
proc processTransactionImpl(
vmState: BaseVMState; ## Parent accounts environment for transaction
tx: Transaction; ## Transaction to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
sender: EthAddress; ## tx.recoverSender
header: BlockHeader; ## Header for the block containing the current tx
): Result[GasInt, string] =
## Modelled after `https://eips.ethereum.org/EIPS/eip-1559#specification`_
@ -267,7 +267,7 @@ proc processDequeueConsolidationRequests*(vmState: BaseVMState): seq[Request] =
proc processTransaction*(
vmState: BaseVMState; ## Parent accounts environment for transaction
tx: Transaction; ## Transaction to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
sender: EthAddress; ## tx.recoverSender
header: BlockHeader; ## Header for the block containing the current tx
): Result[GasInt,string] =
vmState.processTransactionImpl(tx, sender, header)

View File

@ -18,7 +18,7 @@ import
../../utils/utils,
../../transaction,
./tx_info,
eth/[common, keys],
eth/common/transaction_utils,
results
{.push raises: [].}
@ -62,7 +62,7 @@ proc init*(item: TxItemRef; status: TxItemStatus; info: string) =
proc new*(T: type TxItemRef; tx: PooledTransaction; itemID: Hash256;
status: TxItemStatus; info: string): Result[T,void] {.gcsafe,raises: [].} =
## Create item descriptor.
let rc = tx.tx.ecRecover
let rc = tx.tx.recoverSender()
if rc.isErr:
return err()
ok(T(itemID: itemID,

View File

@ -248,7 +248,7 @@ proc validateTxBasic*(
proc validateTransaction*(
roDB: ReadOnlyStateDB; ## Parent accounts environment for transaction
tx: Transaction; ## tx to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
sender: EthAddress; ## tx.recoverSender
maxLimit: GasInt; ## gasLimit from block header
baseFee: UInt256; ## baseFee from block header
excessBlobGas: uint64; ## excessBlobGas from parent block header

View File

@ -22,7 +22,8 @@ import
../common/common,
../transaction/call_evm,
../core/[tx_pool, tx_pool/tx_item],
../utils/utils
../utils/utils,
eth/common/transaction_utils
from eth/p2p import EthereumNode
export httpserver
@ -629,8 +630,7 @@ proc txFrom(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
else:
tx.blockNumber
var sender: EthAddress
if not getSender(tx.tx, sender):
let sender = tx.tx.recoverSender.valueOr:
return ok(respNull())
let hres = ctx.getBlockByNumber(blockNumber)
if hres.isErr:
@ -732,8 +732,7 @@ proc txCumulativeGasUsed(ud: RootRef, params: Args, parent: Node): RespResult {.
proc txCreatedContract(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let ctx = GraphqlContextRef(ud)
let tx = TxNode(parent)
var sender: EthAddress
if not getSender(tx.tx, sender):
let sender = tx.tx.recoverSender.valueOr:
return err("can't calculate sender")
if not tx.tx.contractCreation:

View File

@ -234,9 +234,9 @@ proc setupEthRpc*(
let
accDB = stateDBFromTag(blockId("latest"))
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1)
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1, com.chainId)
eip155 = com.isEIP155(com.syncCurrent)
signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155)
signedTx = signTransaction(tx, acc.privateKey, eip155)
result = rlp.encode(signedTx)
server.rpc("eth_sendTransaction") do(data: TransactionArgs) -> Web3Hash:
@ -254,9 +254,9 @@ proc setupEthRpc*(
let
accDB = stateDBFromTag(blockId("latest"))
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1)
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1, com.chainId)
eip155 = com.isEIP155(com.syncCurrent)
signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155)
signedTx = signTransaction(tx, acc.privateKey, eip155)
networkPayload =
if signedTx.txType == TxEip4844:
if data.blobs.isNone or data.commitments.isNone or data.proofs.isNone:

View File

@ -26,7 +26,8 @@ import
../evm/state,
../evm/precompiles,
../evm/tracer/access_list_tracer,
../evm/evm_errors
../evm/evm_errors,
eth/common/transaction_utils
const
@ -91,7 +92,7 @@ proc calculateMedianGasPrice*(chain: CoreDbRef): GasInt
const minGasPrice = 30_000_000_000.GasInt
result = max(result, minGasPrice)
proc unsignedTx*(tx: TransactionArgs, chain: CoreDbRef, defaultNonce: AccountNonce): Transaction
proc unsignedTx*(tx: TransactionArgs, chain: CoreDbRef, defaultNonce: AccountNonce, chainId: ChainId): Transaction
{.gcsafe, raises: [CatchableError].} =
if tx.to.isSome:
result.to = Opt.some(ethAddr(tx.to.get))
@ -117,6 +118,7 @@ proc unsignedTx*(tx: TransactionArgs, chain: CoreDbRef, defaultNonce: AccountNon
result.nonce = defaultNonce
result.payload = tx.payload
result.chainId = chainId
proc toWd(wd: Withdrawal): WithdrawalObject =
WithdrawalObject(
@ -142,7 +144,8 @@ proc populateTransactionObject*(tx: Transaction,
result.blockHash = Opt.some(w3Hash header.blockHash)
result.blockNumber = Opt.some(w3BlockNumber(header.number))
result.`from` = w3Addr tx.getSender()
if (let sender = tx.recoverSender(); sender.isOk):
result.`from` = sender[]
result.gas = w3Qty(tx.gasLimit)
result.gasPrice = w3Qty(tx.gasPrice)
result.hash = w3Hash tx.rlpHash
@ -221,24 +224,22 @@ proc populateBlockObject*(header: BlockHeader, chain: CoreDbRef, fullTx: bool, i
result.parentBeaconBlockRoot = Opt.some(w3Hash header.parentBeaconBlockRoot.get)
proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction,
txIndex: uint64, header: BlockHeader): ReceiptObject
{.gcsafe, raises: [ValidationError].} =
txIndex: uint64, header: BlockHeader): ReceiptObject =
let sender = tx.recoverSender()
result = ReceiptObject()
result.transactionHash = w3Hash tx.rlpHash
result.transactionIndex = w3Qty(txIndex)
result.blockHash = w3Hash header.blockHash
result.blockNumber = w3BlockNumber(header.number)
result.`from` = w3Addr tx.getSender()
if sender.isSome():
result.`from` = sender.get()
result.to = Opt.some(w3Addr tx.destination)
result.cumulativeGasUsed = w3Qty(receipt.cumulativeGasUsed)
result.gasUsed = w3Qty(gasUsed)
result.`type` = Opt.some Quantity(receipt.receiptType)
if tx.contractCreation:
var sender: EthAddress
if tx.getSender(sender):
let contractAddress = generateAddress(sender, tx.nonce)
result.contractAddress = Opt.some(w3Addr contractAddress)
if tx.contractCreation and sender.isSome:
result.contractAddress = Opt.some(tx.creationAddress(sender[]))
for log in receipt.logs:
# TODO: Work everywhere with either `Hash256` as topic or `array[32, byte]`

View File

@ -10,7 +10,7 @@
{.push raises: [].}
import
eth/common/eth_types,
eth/common/[eth_types, transaction_utils],
eth/common/eth_types_rlp,
web3/eth_api_types,
../beacon/web3_eth_conv,
@ -44,9 +44,8 @@ proc populateTransactionObject*(tx: Transaction,
result.blockHash = w3Hash optionalHash
result.blockNumber = w3BlockNumber optionalNumber
var sender: EthAddress
if tx.getSender(sender):
result.`from` = w3Addr sender
if (let sender = tx.recoverSender(); sender.isOk):
result.`from` = sender[]
result.gas = w3Qty(tx.gasLimit)
result.gasPrice = w3Qty(tx.gasPrice)
result.hash = w3Hash tx.rlpHash

View File

@ -15,6 +15,7 @@ import
nimcrypto/utils as ncrutils,
results,
web3/conversions,
eth/common/transaction_utils,
./beacon/web3_eth_conv,
./common/common,
./constants,
@ -187,7 +188,7 @@ proc traceTransactionImpl(
miner = vmState.coinbase()
for idx, tx in transactions:
let sender = tx.getSender
let sender = tx.recoverSender().expect("valid signature")
let recipient = tx.getRecipient(sender)
if idx.uint64 == txIndex:
@ -263,7 +264,7 @@ proc dumpBlockStateImpl(
stateBefore = LedgerRef.init(com.db, parent.stateRoot, storeSlotHash = true)
for idx, tx in blk.transactions:
let sender = tx.getSender
let sender = tx.recoverSender().expect("valid signature")
let recipient = tx.getRecipient(sender)
before.captureAccount(stateBefore, sender, senderName & $idx)
before.captureAccount(stateBefore, recipient, recipientName & $idx)
@ -278,7 +279,7 @@ proc dumpBlockStateImpl(
var stateAfter = vmState.stateDB
for idx, tx in blk.transactions:
let sender = tx.getSender
let sender = tx.recoverSender().expect("valid signature")
let recipient = tx.getRecipient(sender)
after.captureAccount(stateAfter, sender, senderName & $idx)
after.captureAccount(stateAfter, recipient, recipientName & $idx)
@ -329,7 +330,7 @@ proc traceBlockImpl(
for tx in blk.transactions:
let
sender = tx.getSender
sender = tx.recoverSender().expect("valid signature")
rc = vmState.processTransaction(tx, sender, header)
if rc.isOk:
gasUsed = gasUsed + rc.value

View File

@ -6,11 +6,12 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
./constants, ./errors, eth/[common, keys], ./utils/utils,
common/evmforks, ./evm/internals
./[constants, errors],
./common/evmforks,
./evm/interpreter/gas_costs,
eth/common/[addresses, keys, transactions, transactions_rlp, transaction_utils]
import eth/common/transaction as common_transaction
export common_transaction, errors
export addresses, keys, transactions
proc toWordSize(size: GasInt): GasInt =
# Round input to the nearest bigger multiple of 32
@ -48,54 +49,6 @@ proc intrinsicGas*(tx: Transaction, fork: EVMFork): GasInt =
inc(numKeys, n.storageKeys.len)
result += GasInt(numKeys) * ACCESS_LIST_STORAGE_KEY_COST
proc getSignature*(tx: Transaction, output: var Signature): bool =
var bytes: array[65, byte]
bytes[0..31] = tx.R.toBytesBE()
bytes[32..63] = tx.S.toBytesBE()
if tx.txType == TxLegacy:
var v = tx.V
if v >= EIP155_CHAIN_ID_OFFSET:
v = 28 - (v and 0x01)
elif v == 27 or v == 28:
discard
else:
return false
bytes[64] = byte(v - 27)
else:
bytes[64] = tx.V.byte
let sig = Signature.fromRaw(bytes)
if sig.isOk:
output = sig[]
return true
return false
proc toSignature*(tx: Transaction): Signature =
if not getSignature(tx, result):
raise newException(Exception, "Invalid signature")
proc getSender*(tx: Transaction, output: var EthAddress): bool =
## Find the address the transaction was sent from.
var sig: Signature
if tx.getSignature(sig):
var txHash = tx.txHashNoSignature
let pubkey = recover(sig, SkMessage(txHash.data))
if pubkey.isOk:
output = pubkey[].toCanonicalAddress()
result = true
proc getSender*(tx: 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")
proc getRecipient*(tx: Transaction, sender: EthAddress): EthAddress =
if tx.contractCreation:
result = generateAddress(sender, tx.nonce)
else:
result = tx.to.get()
proc validateTxLegacy(tx: Transaction, fork: EVMFork) =
var
vMin = 27'u64
@ -155,6 +108,8 @@ proc validateTxEip7702(tx: Transaction) =
raise newException(ValidationError, "Invalid EIP-7702 transaction")
proc validate*(tx: Transaction, fork: EVMFork) =
# TODO it doesn't seem like this function is called from anywhere except tests
# which feels like it might be a problem (?)
# parameters pass validation rules
if tx.intrinsicGas(fork) > tx.gasLimit:
raise newException(ValidationError, "Insufficient gas")
@ -163,8 +118,10 @@ proc validate*(tx: Transaction, fork: EVMFork) =
raise newException(ValidationError, "Initcode size exceeds max")
# check signature validity
var sender: EthAddress
if not tx.getSender(sender):
# TODO a validation function like this should probably be returning the sender
# since recovering the public key accounts for ~10% of block processing
# time (at the time of writing)
let sender = tx.recoverSender().valueOr:
raise newException(ValidationError, "Invalid signature or failed message verification")
case tx.txType
@ -177,27 +134,9 @@ proc validate*(tx: Transaction, fork: EVMFork) =
of TxEip7702:
validateTxEip7702(tx)
proc signTransaction*(tx: Transaction, privateKey: PrivateKey, chainId: ChainId, eip155: bool): Transaction =
proc signTransaction*(tx: Transaction, privateKey: PrivateKey, eip155 = true): Transaction =
result = tx
if eip155:
# trigger rlpEncodeEIP155 in nim-eth
result.V = chainId.uint64 * 2'u64 + 35'u64
let
rlpTx = rlpEncode(result)
sig = sign(privateKey, rlpTx).toRaw
case tx.txType
of TxLegacy:
if eip155:
result.V = sig[64].uint64 + result.V
else:
result.V = sig[64].uint64 + 27'u64
else:
result.V = sig[64].uint64
result.R = UInt256.fromBytesBE(sig[0..31])
result.S = UInt256.fromBytesBE(sig[32..63])
result.signature = result.sign(privateKey, eip155)
# deriveChainId derives the chain id from the given v parameter
func deriveChainId*(v: uint64, chainId: ChainId): ChainId =

View File

@ -51,23 +51,6 @@ type
# Private helpers
# ------------------------------------------------------------------------------
proc vrsSerialised(tx: Transaction): Result[array[65,byte],UtilsError] =
## Parts copied from `transaction.getSignature`.
var data: array[65,byte]
data[0..31] = tx.R.toBytesBE
data[32..63] = tx.S.toBytesBE
if tx.txType != TxLegacy:
data[64] = tx.V.byte
elif tx.V >= EIP155_CHAIN_ID_OFFSET:
data[64] = byte(1 - (tx.V and 1))
elif tx.V == 27 or tx.V == 28:
data[64] = byte(tx.V - 27)
else:
return err((errSigPrefixError,"")) # legacy error
ok(data)
proc encodePreSealed(header: BlockHeader): seq[byte] =
## Cut sigature off `extraData` header field.
if header.extraData.len < EXTRA_SEAL:
@ -113,22 +96,6 @@ proc ecRecover*(header: BlockHeader): EcAddrResult =
## the argument header.
header.extraData.recoverImpl(header.hashPreSealed)
proc ecRecover*(tx: var Transaction): EcAddrResult =
## Extracts sender address from transaction. This function has similar
## functionality as `transaction.getSender()`.
let txSig = tx.vrsSerialised
if txSig.isErr:
return err(txSig.error)
try:
result = txSig.value.recoverImpl(tx.txHashNoSignature)
except ValueError as ex:
return err((errTxEncError, ex.msg))
proc ecRecover*(tx: Transaction): EcAddrResult =
## Variant of `ecRecover()` for call-by-value header.
var ty = tx
ty.ecRecover
# ------------------------------------------------------------------------------
# Public constructor for caching ecRecover version
# ------------------------------------------------------------------------------

View File

@ -9,7 +9,7 @@
# according to those terms.
import
json, strutils, os,
json, strutils, os, eth/common/transaction_utils,
eth/common, httputils, nimcrypto/utils,
stint, stew/byteutils
@ -196,7 +196,7 @@ proc parseWithdrawal*(n: JsonNode): Withdrawal =
n.fromJson "amount", result.amount
proc validateTxSenderAndHash*(n: JsonNode, tx: Transaction) =
var sender = tx.getSender()
var sender = tx.recoverSender().expect("valid signature")
var fromAddr: EthAddress
n.fromJson "from", fromAddr
doAssert sender.to0xHex == fromAddr.to0xHex

View File

@ -10,7 +10,7 @@
import
json, strutils, os,
chronicles, eth/common,
chronicles, eth/common, eth/common/transaction_utils,
../nimbus/transaction, ../nimbus/launcher,
./js_tracer, ./parser, ./downloader
@ -157,7 +157,8 @@ proc requestPostState*(premix, n: JsonNode, blockNumber: BlockNumber) =
for t in txs:
var txKind = TxKind.Regular
let tx = parseTransaction(t)
let sender = tx.getSender
let sender = tx.recoverSender().valueOr:
raise (ref ValueError)(msg: "Invalid tx signature")
if tx.contractCreation: txKind = TxKind.ContractCreation
if hasInternalTx(tx, blockNumber, sender):
let txTrace = requestInternalTx(t["hash"], tracer)

View File

@ -37,7 +37,6 @@ cliBuilder:
#./test_txpool, -- fails
./test_txpool2,
./test_engine_api,
./test_eip4844,
./test_getproof_json,
./test_aristo,
./test_coredb

View File

@ -10,7 +10,7 @@
import
std/[macrocache, strutils],
eth/keys,
eth/common/[keys, transaction_utils],
unittest2,
chronicles,
stew/byteutils,
@ -384,9 +384,10 @@ proc createSignedTx(payload: Blob, chainId: ChainId): Transaction =
to: Opt.some codeAddress,
value: 500.u256,
payload: payload,
chainId: chainId,
versionedHashes: @[VersionedHash(EMPTY_UNCLE_HASH), VersionedHash(EMPTY_SHA3)]
)
signTransaction(unsignedTx, privateKey, chainId, false)
signTransaction(unsignedTx, privateKey, false)
proc runVM*(vmState: BaseVMState, boa: Assembler): bool =
let
@ -395,7 +396,7 @@ proc runVM*(vmState: BaseVMState, boa: Assembler): bool =
db.setCode(codeAddress, boa.code)
db.setBalance(codeAddress, 1_000_000.u256)
let tx = createSignedTx(boa.data, com.chainId)
let asmResult = testCallEvm(tx, tx.getSender, vmState)
let asmResult = testCallEvm(tx, tx.recoverSender().expect("valid signature"), vmState)
verifyAsmResult(vmState, boa, asmResult)
macro assembler*(list: untyped): untyped =

View File

@ -106,7 +106,7 @@ proc main() {.used.} =
#chainDB.dumpTest(2_283_416) # first DDOS spam attack block
com.dumpTest(2_463_413) # tangerine call* gas cost bug
com.dumpTest(2_675_000) # spurious dragon first block
com.dumpTest(2_675_002) # EIP155 tx.getSender
com.dumpTest(2_675_002) # EIP155 tx.recoverSender
com.dumpTest(4_370_000) # Byzantium first block
when isMainModule:

View File

@ -1,157 +0,0 @@
# Nimbus
# Copyright (c) 2023-2024 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
stew/byteutils,
unittest2,
eth/[common, keys],
../nimbus/transaction
const
recipient = address"095e7baea6a6c7c4c2dfeb977efac326af552d87"
source = address"0x0000000000000000000000000000000000000001"
storageKey= default(StorageKey)
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
abcdef = hexToSeqByte("abcdef")
hexKey = "af1a9be9f1a54421cac82943820a0fe0f601bb5f4f6d0bccc81c613f0ce6ae22"
senderTop = address"73cf19657412508833f618a15e8251306b3e6ee5"
proc tx0(i: int): Transaction =
Transaction(
txType: TxLegacy,
nonce: i.AccountNonce,
to: Opt.some recipient,
gasLimit: 1.GasInt,
gasPrice: 2.GasInt,
payload: abcdef)
proc tx1(i: int): Transaction =
Transaction(
# Legacy tx contract creation.
txType: TxLegacy,
nonce: i.AccountNonce,
gasLimit: 1.GasInt,
gasPrice: 2.GasInt,
payload: abcdef)
proc tx2(i: int): Transaction =
Transaction(
# Tx with non-zero access list.
txType: TxEip2930,
chainId: 1.ChainId,
nonce: i.AccountNonce,
to: Opt.some recipient,
gasLimit: 123457.GasInt,
gasPrice: 10.GasInt,
accessList: accesses,
payload: abcdef)
proc tx3(i: int): Transaction =
Transaction(
# Tx with empty access list.
txType: TxEip2930,
chainId: 1.ChainId,
nonce: i.AccountNonce,
to: Opt.some recipient,
gasLimit: 123457.GasInt,
gasPrice: 10.GasInt,
payload: abcdef)
proc tx4(i: int): Transaction =
Transaction(
# Contract creation with access list.
txType: TxEip2930,
chainId: 1.ChainId,
nonce: i.AccountNonce,
gasLimit: 123457.GasInt,
gasPrice: 10.GasInt,
accessList: accesses)
proc tx5(i: int): Transaction =
Transaction(
txType: TxEip1559,
chainId: 1.ChainId,
nonce: i.AccountNonce,
gasLimit: 123457.GasInt,
maxPriorityFeePerGas: 42.GasInt,
maxFeePerGas: 10.GasInt,
accessList: accesses)
proc tx6(i: int): Transaction =
const
digest = hash32"010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014"
Transaction(
txType: TxEip4844,
chainId: 1.ChainId,
nonce: i.AccountNonce,
gasLimit: 123457.GasInt,
maxPriorityFeePerGas:42.GasInt,
maxFeePerGas: 10.GasInt,
accessList: accesses,
versionedHashes: @[digest]
)
proc tx7(i: int): Transaction =
const
digest = hash32"01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e"
Transaction(
txType: TxEip4844,
chainID: 1.ChainId,
nonce: i.AccountNonce,
gasLimit: 123457.GasInt,
maxPriorityFeePerGas:42.GasInt,
maxFeePerGas: 10.GasInt,
accessList: accesses,
versionedHashes: @[digest],
maxFeePerBlobGas: 10000000.u256,
)
proc tx8(i: int): Transaction =
const
digest = hash32"01624652859a6e98ffc1608e2af0147ca4e86e1ce27672d8d3f3c9d4ffd6ef7e"
Transaction(
txType: TxEip4844,
chainID: 1.ChainId,
nonce: i.AccountNonce,
to: Opt.some recipient,
gasLimit: 123457.GasInt,
maxPriorityFeePerGas:42.GasInt,
maxFeePerGas: 10.GasInt,
accessList: accesses,
versionedHashes: @[digest],
maxFeePerBlobGas: 10000000.u256,
)
proc privKey(keyHex: string): PrivateKey =
let kRes = PrivateKey.fromHex(keyHex)
if kRes.isErr:
echo kRes.error
quit(QuitFailure)
kRes.get()
proc eip4844Main*() =
var signerKey = privKey(hexKey)
suite "EIP4844 sign transaction":
let txs = @[tx0(3), tx1(3), tx2(3), tx3(3), tx4(3),
tx5(3), tx6(3), tx7(3), tx8(3)]
test "sign transaction":
for tx in txs:
let signedTx = signTransaction(tx, signerKey, 1.ChainId, true)
let sender = signedTx.getSender()
check sender == senderTop
when isMainModule:
eip4844Main()

View File

@ -10,6 +10,7 @@ import
unittest2,
stew/byteutils,
eth/keys,
eth/common/transaction_utils,
../nimbus/common,
../nimbus/transaction,
../nimbus/evm/types,
@ -358,8 +359,8 @@ proc runTestOverflow() =
)
let privateKey = PrivateKey.fromHex("0000000000000000000000000000000000000000000000000000001000000000")[]
let tx = signTransaction(unsignedTx, privateKey, ChainId(1), false)
let res = testCallEvm(tx, tx.getSender, s)
let tx = signTransaction(unsignedTx, privateKey, false)
let res = testCallEvm(tx, tx.recoverSender().expect("valid signature"), s)
when defined(evmc_enabled):
check res.error == "EVMC_FAILURE"

View File

@ -20,6 +20,7 @@ import
../tools/evmstate/helpers,
../tools/common/state_clearing,
eth/trie/trie_defs,
eth/common/transaction_utils,
unittest2,
stew/byteutils,
results
@ -101,7 +102,7 @@ proc testFixtureIndexes(ctx: var TestCtx, testStatusIMPL: var TestStatus) =
)
var gasUsed: GasInt
let sender = ctx.tx.getSender()
let sender = ctx.tx.recoverSender().expect("valid signature")
vmState.mutateStateDB:
setupStateDB(ctx.pre, db)

View File

@ -11,6 +11,7 @@
import
std/[strformat, strutils, importutils],
eth/keys,
eth/common/transaction_utils,
stew/byteutils,
stew/endians2,
../nimbus/config,
@ -53,13 +54,13 @@ proc pp*(a: EthAddress): string =
proc pp*(tx: Transaction): string =
# "(" & tx.ecRecover.value.pp & "," & $tx.nonce & ")"
"(" & tx.getSender.pp & "," & $tx.nonce & ")"
"(" & tx.recoverSender().value().pp & "," & $tx.nonce & ")"
proc pp*(h: KeccakHash): string =
h.data.toHex[52 .. 63].toLowerAscii
proc pp*(tx: Transaction; ledger: LedgerRef): string =
let address = tx.getSender
let address = tx.recoverSender().value()
"(" & address.pp &
"," & $tx.nonce &
";" & $ledger.getNonce(address) &
@ -134,7 +135,7 @@ func makeTx(
)
inc env.nonce
signTransaction(tx, env.vaultKey, env.chainId, eip155 = true)
signTransaction(tx, env.vaultKey, eip155 = true)
func initAddr(z: int): EthAddress =
const L = sizeof(result)

View File

@ -9,6 +9,7 @@ import
std/[strformat, strutils, json, os, tables, macros],
unittest2, stew/byteutils,
eth/[keys, trie],
eth/common/transaction_utils,
../nimbus/common/common,
../tools/common/helpers as chp,
../nimbus/[evm/computation,
@ -42,10 +43,11 @@ template doTest(fixture: JsonNode; vmState: BaseVMState; address: PrecompileAddr
gasLimit: 1_000_000_000.GasInt,
to: Opt.some initAddress(address.byte),
value: 0.u256,
chainId: ChainId(1),
payload: if dataStr.len > 0: dataStr.hexToSeqByte else: @[]
)
let tx = signTransaction(unsignedTx, privateKey, ChainId(1), false)
let fixtureResult = testCallEvm(tx, tx.getSender, vmState)
let tx = signTransaction(unsignedTx, privateKey, false)
let fixtureResult = testCallEvm(tx, tx.recoverSender().expect("valid signature"), vmState)
if expectedErr:
check fixtureResult.isError

View File

@ -12,6 +12,7 @@ import
json_rpc/[rpcserver, rpcclient],
nimcrypto/[keccak, hash],
eth/[rlp, keys, trie/hexary_proof_verification],
eth/common/transaction_utils,
../nimbus/[constants, transaction, config, evm/state, evm/types, version],
../nimbus/db/[ledger, storage_types],
../nimbus/sync/protocol,
@ -139,7 +140,8 @@ proc setupEnv(com: CommonRef, signer, ks2: EthAddress, ctx: EthContext): TestEnv
gasPrice: 30_000_000_000,
gasLimit: 70_000,
value : 1.u256,
to : some(zeroAddress)
to : some(zeroAddress),
chainId : com.chainId,
)
unsignedTx2 = Transaction(
txType : TxLegacy,
@ -147,11 +149,12 @@ proc setupEnv(com: CommonRef, signer, ks2: EthAddress, ctx: EthContext): TestEnv
gasPrice: 30_000_000_100,
gasLimit: 70_000,
value : 2.u256,
to : some(zeroAddress)
to : some(zeroAddress),
chainId : com.chainId,
)
eip155 = com.isEIP155(com.syncCurrent)
signedTx1 = signTransaction(unsignedTx1, acc.privateKey, com.chainId, eip155)
signedTx2 = signTransaction(unsignedTx2, acc.privateKey, com.chainId, eip155)
signedTx1 = signTransaction(unsignedTx1, acc.privateKey, eip155)
signedTx2 = signTransaction(unsignedTx2, acc.privateKey, eip155)
txs = [signedTx1, signedTx2]
let txRoot = calcTxRoot(txs)
@ -160,7 +163,7 @@ proc setupEnv(com: CommonRef, signer, ks2: EthAddress, ctx: EthContext): TestEnv
vmState.receipts = newSeq[Receipt](txs.len)
vmState.cumulativeGasUsed = 0
for txIndex, tx in txs:
let sender = tx.getSender()
let sender = tx.recoverSender().expect("valid signature")
let rc = vmState.processTransaction(tx, sender, vmHeader)
doAssert(rc.isOk, "Invalid transaction: " & rc.error)
vmState.receipts[txIndex] = makeReceipt(vmState, tx.txType)
@ -400,7 +403,7 @@ proc rpcMain*() =
let signedTxBytes = await client.eth_signTransaction(unsignedTx)
let signedTx = rlp.decode(signedTxBytes, Transaction)
check signer == signedTx.getSender() # verified
check signer == signedTx.recoverSender().expect("valid signature") # verified
let hashAhex = await client.eth_sendTransaction(unsignedTx)
let hashBhex = await client.eth_sendRawTransaction(signedTxBytes)

View File

@ -13,6 +13,7 @@ import
unittest2,
eth/rlp,
./test_helpers,
eth/common/transaction_utils,
../nimbus/[errors, transaction],
../nimbus/utils/utils
@ -29,7 +30,7 @@ when isMainModule:
transactionJsonMain()
proc txHash(tx: Transaction): string =
toLowerAscii($keccakHash(rlp.encode(tx)))
rlpHash(tx).toHex()
proc testTxByFork(tx: Transaction, forkData: JsonNode, forkName: string, testStatusIMPL: var TestStatus) =
try:
@ -41,7 +42,7 @@ proc testTxByFork(tx: Transaction, forkData: JsonNode, forkName: string, testSta
let sender = EthAddress.fromHex(forkData["sender"].getStr)
check "hash" in forkData
check tx.txHash == forkData["hash"].getStr
check tx.getSender == sender
check tx.recoverSender().expect("valid signature") == sender
func noHash(fixture: JsonNode): bool =
result = true

View File

@ -16,6 +16,7 @@ import
../../nimbus/utils/ec_recover,
../../nimbus/core/tx_pool/[tx_chain, tx_item],
../../nimbus/transaction,
eth/common/transaction_utils,
./helpers,
eth/[keys, p2p],
stew/[keyed_queue, byteutils]
@ -69,7 +70,7 @@ proc fillGenesis(env: var TxEnv, param: NetworkParams) =
for z in n:
let bytes = hexToSeqByte(z.getStr)
let tx = rlp.decode(bytes, Transaction)
let sender = tx.getSender()
let sender = tx.recoverSender().expect("valid signature")
let bal = map.getOrDefault(sender, 0.u256)
if bal + tx.value > 0:
map[sender] = bal + tx.value
@ -99,10 +100,10 @@ proc setupTxPool*(getStatus: proc(): TxItemStatus): (CommonRef, TxPoolRef, int)
let txPool = TxPoolRef.new(com)
for n, tx in txEnv.txs:
let s = txEnv.getSigner(tx.getSender())
let s = txEnv.getSigner(tx.recoverSender().expect("valid signature"))
let status = statusInfo[getStatus()]
let info = &"{n}/{txEnv.txs.len} {status}"
let signedTx = signTransaction(tx, s.signer, txEnv.chainId, eip155 = true)
let signedTx = signTransaction(tx, s.signer, eip155 = true)
txPool.add(PooledTransaction(tx: signedTx), info)
(com, txPool, txEnv.txs.len)

View File

@ -9,7 +9,7 @@
# according to those terms.
import
../../nimbus/constants,
../../nimbus/[constants, transaction],
../../nimbus/utils/ec_recover,
../../nimbus/core/tx_pool/tx_item,
eth/[common, common/transaction, keys],
@ -20,35 +20,6 @@ const
# example from clique, signer: 658bdf435d810c91414ec09147daa6db62406379
prvKey = "9c647b8b7c4e7c3490668fb6c11473619db80c93704c70893d3813af4090c39c"
proc signature(tx: Transaction; key: PrivateKey): (uint64,UInt256,UInt256) =
let
hashData = tx.txHashNoSignature.data
signature = key.sign(SkMessage(hashData)).toRaw
v = signature[64].uint64
result[1] = UInt256.fromBytesBE(signature[0..31])
result[2] = UInt256.fromBytesBE(signature[32..63])
if tx.txType == TxLegacy:
if tx.V >= EIP155_CHAIN_ID_OFFSET:
# just a guess which does not always work .. see `txModPair()`
# see https://eips.ethereum.org/EIPS/eip-155
result[0] = (tx.V and not 1'u64) or (not v and 1'u64)
else:
result[0] = 27 + v
else:
# currently unsupported, will skip this one .. see `txModPair()`
result[0] = 0'u64
proc sign(tx: Transaction; key: PrivateKey): Transaction =
let (V,R,S) = tx.signature(key)
result = tx
result.V = V
result.R = R
result.S = S
proc sign(header: BlockHeader; key: PrivateKey): BlockHeader =
let
hashData = header.blockHash.data
@ -73,14 +44,14 @@ proc txModPair*(item: TxItemRef; nonce: int; priceBump: int):
tx1.gasPrice = (tx0.gasPrice * (100 + priceBump).GasInt + 99.GasInt) div 100
let
tx0Signed = tx0.sign(prvTestKey)
tx1Signed = tx1.sign(prvTestKey)
tx0Signed = tx0.signTransaction(prvTestKey)
tx1Signed = tx0.signTransaction(prvTestKey)
block:
let rc = tx0Signed.ecRecover
let rc = tx0Signed.recoverSender()
if rc.isErr or rc.value != testAddress:
return
block:
let rc = tx1Signed.ecRecover
let rc = tx1Signed.recoverSender()
if rc.isErr or rc.value != testAddress:
return
(item,tx0Signed,tx1Signed)

View File

@ -77,13 +77,13 @@ func makeTx(
)
inc t.nonce
signTransaction(tx, t.vaultKey, t.chainId, eip155 = true)
signTransaction(tx, t.vaultKey, eip155 = true)
func signTxWithNonce(
t: TestEnv, tx: Transaction, nonce: AccountNonce): Transaction =
var tx = tx
tx.nonce = nonce
signTransaction(tx, t.vaultKey, t.chainId, eip155 = true)
signTransaction(tx, t.vaultKey, eip155 = true)
proc initEnv(envFork: HardFork): TestEnv =
var

View File

@ -12,6 +12,7 @@ import
std/[json, strutils, sets, tables, options, streams],
chronicles,
eth/keys,
eth/common/transaction_utils,
stew/byteutils,
results,
stint,
@ -68,7 +69,7 @@ method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256
keccakHash(toBytes($blockNumber))
proc verifyResult(ctx: var StateContext, vmState: BaseVMState, obtainedHash: Hash256) =
ctx.error = ""
ctx.error = ""
if obtainedHash != ctx.expectedHash:
ctx.error = "post state root mismatch: got $1, want $2" %
[($obtainedHash).toLowerAscii, $ctx.expectedHash]
@ -129,7 +130,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
tracer = tracer)
var gasUsed: GasInt
let sender = ctx.tx.getSender()
let sender = ctx.tx.recoverSender().expect("valid signature")
vmState.mutateStateDB:
setupStateDB(pre, db)

View File

@ -147,7 +147,7 @@ proc parseTx*(n: JsonNode, dataIndex, gasIndex, valueIndex: int): Transaction =
tx.to = Opt.some(EthAddress.fromHex(rawTo))
let secretKey = required(PrivateKey, "secretKey")
signTransaction(tx, secretKey, tx.chainId, false)
signTransaction(tx, secretKey, false)
proc parseTx*(txData, index: JsonNode): Transaction =
let

View File

@ -228,6 +228,7 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
if n.hasKey("to"):
tx.to = Opt.some(EthAddress.fromJson(n, "to"))
tx.chainId = chainId
case tx.txType
of TxLegacy:
@ -262,7 +263,7 @@ proc parseTx(n: JsonNode, chainId: ChainID): Transaction =
if n.hasKey("secretKey"):
let data = Blob.fromJson(n, "secretKey")
let secretKey = PrivateKey.fromRaw(data).tryGet
signTransaction(tx, secretKey, chainId, eip155)
signTransaction(tx, secretKey, eip155)
else:
required(tx, uint64, v)
required(tx, UInt256, r)

View File

@ -11,6 +11,7 @@
import
std/[json, strutils, tables, os, streams],
eth/[rlp, trie, eip1559],
eth/common/transaction_utils,
stint, results,
"."/[config, types, helpers],
../common/state_clearing,
@ -256,8 +257,7 @@ proc exec(ctx: var TransContext,
continue
let tx = txRes.get
var sender: EthAddress
if not tx.getSender(sender):
let sender = tx.recoverSender().valueOr:
rejected.add RejectedTx(
index: txIndex,
error: "Could not get sender"

View File

@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2022 Status Research & Development GmbH
# Copyright (c) 2022-2024 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)
@ -11,6 +11,7 @@
import
eth/[common, rlp],
stew/byteutils,
eth/common/transaction_utils,
../../nimbus/transaction,
../../nimbus/common/evmforks
@ -19,7 +20,7 @@ proc parseTx(hexLine: string) =
let
bytes = hexToSeqByte(hexLine)
tx = decodeTx(bytes)
address = tx.getSender()
address = tx.recoverSender().expect("valid signature")
tx.validate(FkLondon)
@ -30,8 +31,6 @@ proc parseTx(hexLine: string) =
echo "err: ", ex.msg
except ValueError as ex:
echo "err: ", ex.msg
except ValidationError as ex:
echo "err: ", ex.msg
except Exception:
# TODO: rlp.hasData assertion should be
# changed into RlpError

2
vendor/nim-eth vendored

@ -1 +1 @@
Subproject commit 792b8b9bff09a5ed7cc98bc12b0c3d991e019b6f
Subproject commit 4ea11b9fb9c6a0ab0886b5deca94a3d4f669386d

2
vendor/nim-web3 vendored

@ -1 +1 @@
Subproject commit 62a0005b0907a64090827d4e5d691682587f5b2a
Subproject commit c38791832cac2d23eab57cdc32decdd8123e5d36