This commit is contained in:
Etan Kissling 2024-05-13 11:00:13 +03:00
parent b54ec688dd
commit b0158c0619
No known key found for this signature in database
GPG Key ID: B21DA824C5A3D03D
79 changed files with 1121 additions and 916 deletions

4
.gitmodules vendored
View File

@ -102,7 +102,7 @@
path = vendor/nim-web3
url = https://github.com/status-im/nim-web3.git
ignore = dirty
branch = master
branch = feat/eip-6493
[submodule "vendor/nim-snappy"]
path = vendor/nim-snappy
url = https://github.com/status-im/nim-snappy.git
@ -172,7 +172,7 @@
path = vendor/nim-ssz-serialization
url = https://github.com/status-im/nim-ssz-serialization.git
ignore = untracked
branch = master
branch = feat/eip-6493
[submodule "vendor/nimbus-eth2"]
path = vendor/nimbus-eth2
url = https://github.com/status-im/nimbus-eth2.git

View File

@ -21,7 +21,8 @@ import
proc processChainData(cd: ChainData): TestStatus =
let
networkId = NetworkId(cd.params.config.chainId)
com = CommonRef.new(newCoreDbRef DefaultDbMemory,
com = CommonRef.new(
newCoreDbRef(DefaultDbMemory, cd.params.config.chainId),
pruneTrie = false,
networkId,
cd.params

View File

@ -11,6 +11,7 @@
import
std/[options, strutils, typetraits],
stew/byteutils,
eth/common/transaction,
./blobs,
../types,
../tx_sender,
@ -656,10 +657,11 @@ proc generateInvalidPayload*(sender: TxSender, data: ExecutableData, payloadFiel
case payloadField
of InvalidTransactionSignature:
var sig = CustSig(R: baseTx.R - 1.u256)
var sig = CustSig(
R: ecdsa_unpack_signature(baseTx.signature.ecdsa_signature).r - 1.u256)
custTx.signature = some(sig)
of InvalidTransactionNonce:
custTx.nonce = some(baseTx.nonce - 1)
custTx.nonce = some(baseTx.payload.nonce - 1)
of InvalidTransactionGas:
custTx.gas = some(0.GasInt)
of InvalidTransactionGasPrice:
@ -670,11 +672,12 @@ proc generateInvalidPayload*(sender: TxSender, data: ExecutableData, payloadFiel
# Vault account initially has 0x123450000000000000000, so this value should overflow
custTx.value = some(UInt256.fromHex("0x123450000000000000001"))
of InvalidTransactionChainID:
custTx.chainId = some(ChainId(baseTx.chainId.uint64 + 1))
custTx.chainId = some(ChainId(sender.chainId.uint64 + 1))
else: discard
let acc = sender.getNextAccount()
let modifiedTx = sender.customizeTransaction(acc, baseTx, custTx)
let modifiedTx = sender.customizeTransaction(
acc, baseTx, custTx, sender.chainId)
customPayloadMod = CustomPayloadData(
transactions: some(@[modifiedTx]),
)

View File

@ -12,7 +12,7 @@ import
std/[tables, strutils, typetraits],
stint,
eth/[common, rlp],
eth/common/eth_types_rlp,
eth/common/[eth_types_rlp, transaction],
chronicles,
stew/[results, byteutils],
kzg4844/kzg_ex as kzg,
@ -53,8 +53,9 @@ func getMinExcessBlobGasForBlobGasPrice(data_gas_price: uint64): uint64 =
func getMinExcessBlobsForBlobGasPrice*(data_gas_price: uint64): uint64 =
return getMinExcessBlobGasForBlobGasPrice(data_gas_price) div GAS_PER_BLOB.uint64
proc addBlobTransaction*(pool: TestBlobTxPool, tx: PooledTransaction) =
let txHash = rlpHash(tx)
proc addBlobTransaction*(
pool: TestBlobTxPool, tx: PooledTransaction, chainId: ChainId) =
let txHash = tx.tx.compute_tx_hash(chainId)
pool.transactions[txHash] = tx
proc `==`(a: openArray[AccessTuple], b: openArray[AccessPair]): bool =
@ -73,7 +74,10 @@ proc `==`(a: openArray[AccessTuple], b: openArray[AccessPair]): bool =
return true
# Test two different transactions with the same blob, and check the blob bundle.
proc verifyTransactionFromNode*(client: RpcClient, tx: Transaction): Result[void, string] =
proc verifyTransactionFromNode*(
client: RpcClient,
tx: Transaction,
chainId: ChainId): Result[void, string] =
let txHash = tx.rlpHash
let res = client.txByHash(txHash)
if res.isErr:
@ -81,29 +85,34 @@ proc verifyTransactionFromNode*(client: RpcClient, tx: Transaction): Result[void
let returnedTx = res.get()
# Verify that the tx fields are all the same
if returnedTx.nonce != tx.nonce:
return err("nonce mismatch: $1 != $2" % [$returnedTx.nonce, $tx.nonce])
if returnedTx.nonce != tx.payload.nonce:
return err("nonce mismatch: $1 != $2" %
[$returnedTx.nonce, $tx.payload.nonce])
if returnedTx.gasLimit != tx.gasLimit:
return err("gas mismatch: $1 != $2" % [$returnedTx.gasLimit, $tx.gasLimit])
if returnedTx.gasLimit != tx.payload.gas:
return err("gas mismatch: $1 != $2" %
[$returnedTx.gasLimit, $tx.payload.gas])
if returnedTx.gasPrice != tx.gasPrice:
return err("gas price mismatch: $1 != $2" % [$returnedTx.gasPrice, $tx.gasPrice])
if returnedTx.gasPrice != tx.payload.max_fee_per_gas:
return err("gas price mismatch: $1 != $2" %
[$returnedTx.gasPrice, $tx.payload.max_fee_per_gas])
if returnedTx.value != tx.value:
return err("value mismatch: $1 != $2" % [$returnedTx.value, $tx.value])
if returnedTx.value != tx.payload.value:
return err("value mismatch: $1 != $2" %
[$returnedTx.value, $tx.payload.value])
if returnedTx.to != tx.to:
return err("to mismatch: $1 != $2" % [$returnedTx.to, $tx.to])
if returnedTx.to != tx.payload.to:
return err("to mismatch: $1 != $2" % [$returnedTx.to, $tx.payload.to])
if returnedTx.payload != tx.payload:
return err("data mismatch: $1 != $2" % [returnedTx.payload.toHex, tx.payload.toHex])
if returnedTx.payload != tx.payload.input:
return err("data mismatch: $1 != $2" %
[returnedTx.payload.input.toHex, tx.payload.input.toHex])
if returnedTx.accessList.isNone:
if returnedTx.payload.access_list.isNone:
return err("expect accessList is some")
let ac = returnedTx.accessList.get
if ac != tx.accessList:
let ac = returnedTx.payload.access_list.get
if ac != tx.payload.access_list:
return err("access list mismatch")
if returnedTx.chainId.isNone:

View File

@ -12,7 +12,7 @@ import
std/[tables],
chronicles,
stew/[byteutils],
eth/common, chronos,
eth/[common, common/transaction], chronos,
json_rpc/rpcclient,
web3/execution_types,
../../../nimbus/beacon/web3_eth_conv,
@ -94,8 +94,9 @@ type
proc collectBlobHashes(list: openArray[Web3Tx]): seq[common.Hash256] =
for w3tx in list:
let tx = ethTx(w3tx)
for h in tx.versionedHashes:
result.add h
if tx.payload.blob_versioned_hashes.isSome:
for h in tx.payload.blob_versioned_hashes.unsafeGet:
result.add h
func latestExecutableData*(cl: CLMocker): ExecutableData =
ExecutableData(
@ -373,12 +374,14 @@ proc getNextPayload(cl: CLMocker): bool =
return true
func versionedHashes(payload: ExecutionPayload): seq[Web3Hash] =
func versionedHashes(payload: ExecutionPayload, chainId: ChainId): seq[Web3Hash] =
result = newSeqOfCap[BlockHash](payload.transactions.len)
for x in payload.transactions:
let tx = rlp.decode(distinctBase(x), Transaction)
for vs in tx.versionedHashes:
result.add w3Hash vs
let tx = Transaction.fromBytes(distinctBase(x), chainId).valueOr:
raise (ref MalformedRlpError)(msg: "Invalid transaction in payload")
if tx.payload.blob_versioned_hashes.isSome:
for vs in tx.payload.blob_versioned_hashes.get:
result.add w3Hash vs
proc broadcastNewPayload(cl: CLMocker,
eng: EngineEnv,
@ -387,10 +390,10 @@ proc broadcastNewPayload(cl: CLMocker,
of Version.V1: return eng.client.newPayloadV1(payload.V1)
of Version.V2: return eng.client.newPayloadV2(payload.V2)
of Version.V3: return eng.client.newPayloadV3(payload.V3,
versionedHashes(payload),
versionedHashes(payload, cl.com.chainId),
cl.latestPayloadAttributes.parentBeaconBlockRoot.get)
of Version.V4: return eng.client.newPayloadV4(payload.V4,
versionedHashes(payload),
versionedHashes(payload, cl.com.chainId),
cl.latestPayloadAttributes.parentBeaconBlockRoot.get)
proc broadcastNextNewPayload(cl: CLMocker): bool =

View File

@ -10,6 +10,7 @@
import
chronicles,
eth/common/transaction,
./engine_spec,
../helper,
../cancun/customizer,
@ -304,8 +305,9 @@ method getName(cs: PayloadBuildAfterInvalidPayloadTest): string =
proc collectBlobHashes(list: openArray[Web3Tx]): seq[common.Hash256] =
for w3tx in list:
let tx = ethTx(w3tx)
for h in tx.versionedHashes:
result.add h
if tx.payload.blob_versioned_hashes.isSome:
for h in tx.payload.blob_versioned_hashes.unsafeGet:
result.add h
method execute(cs: PayloadBuildAfterInvalidPayloadTest, env: TestEnv): bool =
# Add a second client to build the invalid payload
@ -442,7 +444,8 @@ method execute(cs: InvalidTxChainIDTest, env: TestEnv): bool =
testCond pbRes
# Verify that the latest payload built does NOT contain the invalid chain Tx
let txHash = shadow.invalidTx.rlpHash
let txHash = shadow.invalidTx.tx.compute_tx_hash(
env.conf.networkParams.config.chainId)
if txInPayload(env.clMock.latestPayloadBuilt, txHash):
fatal "Invalid chain ID tx was included in payload"
return false

View File

@ -70,7 +70,9 @@ method execute(cs: PrevRandaoTransactionTest, env: TestEnv): bool =
onForkchoiceBroadcast: proc(): bool =
# Check the transaction tracing, which is client specific
let expectedPrevRandao = env.clMock.prevRandaoHistory[env.clMock.latestHeader.blockNumber.truncate(uint64)+1]
let res = debugPrevRandaoTransaction(env.engine.client, shadow.txs[shadow.currentTxIndex-1], expectedPrevRandao)
let res = debugPrevRandaoTransaction(
env.engine.client, shadow.txs[shadow.currentTxIndex-1],
expectedPrevRandao, env.conf.networkParams.config.chainId)
testCond res.isOk:
fatal "Error during transaction tracing", msg=res.error

View File

@ -9,7 +9,7 @@
# according to those terms.
import
eth/common,
eth/[common, common/transaction],
chronicles,
stew/byteutils,
./engine_spec,
@ -134,11 +134,11 @@ method getName(cs: TransactionReOrgTest): string =
name.add ", " & $cs.scenario
return name
proc txHash(shadow: ShadowTx): common.Hash256 =
proc txHash(shadow: ShadowTx, chainId: ChainId): common.Hash256 =
if shadow.tx.isNone:
error "SHADOW TX IS NONE"
return
shadow.tx.get.rlpHash
shadow.tx.get.tx.compute_tx_hash(chainId)
# Test transaction status after a forkchoiceUpdated re-orgs to an alternative hash where a transaction is not present
method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
@ -204,7 +204,8 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
shadow.tx = some(shadow.sendTransaction(i))
# Get the receipt
let receipt = env.engine.client.txReceipt(shadow.txHash)
let receipt = env.engine.client.txReceipt(
shadow.txHash(env.conf.networkParams.config.chainId))
testCond receipt.isErr:
fatal "Receipt obtained before tx included in block"
@ -213,7 +214,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
onGetpayload: proc(): bool =
# Check that indeed the payload contains the transaction
if shadow.tx.isSome:
testCond txInPayload(env.clMock.latestPayloadBuilt, shadow.txHash):
testCond txInPayload(
env.clMock.latestPayloadBuilt,
shadow.txHash(env.conf.networkParams.config.chainId)):
fatal "Payload built does not contain the transaction"
if cs.scenario in [TransactionReOrgScenarioReOrgDifferentBlock, TransactionReOrgScenarioNewPayloadOnRevert]:
@ -256,7 +259,8 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
g.expectNoError()
let payload = g.get.executionPayload
testCond txInPayload(payload, shadow.nextTx.rlpHash):
testCond txInPayload(payload, shadow.nextTx.tx.compute_tx_hash(
env.conf.networkParams.config.chainId)):
fatal "Payload built does not contain the transaction"
# Send the new payload and forkchoiceUpdated to it
@ -273,7 +277,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
onNewPayloadBroadcast: proc(): bool =
if shadow.tx.isSome:
# Get the receipt
let receipt = env.engine.client.txReceipt(shadow.txHash)
let receipt = env.engine.client.txReceipt(
shadow.tx.unsafeGet.tx.compute_tx_hash(
env.conf.networkParams.config.chainId))
testCond receipt.isErr:
fatal "Receipt obtained before tx included in block (NewPayload)"
return true
@ -282,7 +288,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
if cs.scenario != TransactionReOrgScenarioReOrgBackIn:
# Transaction is now in the head of the canonical chain, re-org and verify it's removed
# Get the receipt
var txt = env.engine.client.txReceipt(shadow.txHash)
var txt = env.engine.client.txReceipt(
shadow.tx.get.tx.compute_tx_hash(
env.conf.networkParams.config.chainId))
txt.expectBlockHash(ethHash env.clMock.latestForkchoice.headBlockHash)
testCond shadow.payload.parentHash == env.clMock.latestPayloadBuilt.parentHash:
@ -311,7 +319,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
let p = env.engine.client.namedHeader(Head)
p.expectHash(ethHash shadow.payload.blockHash)
txt = env.engine.client.txReceipt(shadow.txHash)
txt = env.engine.client.txReceipt(
shadow.tx.get.tx.compute_tx_hash(
env.conf.networkParams.config.chainId))
if cs.scenario == TransactionReOrgScenarioReOrgOut:
testCond txt.isErr:
fatal "Receipt was obtained when the tx had been re-org'd out"
@ -328,7 +338,9 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
if shadow.tx.isSome:
# Now it should be back with main payload
let txt = env.engine.client.txReceipt(shadow.txHash)
let txt = env.engine.client.txReceipt(
shadow.tx.get.tx.compute_tx_hash(
env.conf.networkParams.config.chainId))
txt.expectBlockHash(ethHash env.clMock.latestForkchoice.headBlockHash)
if cs.scenario != TransactionReOrgScenarioReOrgBackIn:
@ -347,11 +359,16 @@ method execute(cs: TransactionReOrgTest, env: TestEnv): bool =
# Produce one last block and verify that the block contains the transaction
let pbRes = env.clMock.produceSingleBlock(BlockProcessCallbacks(
onForkchoiceBroadcast: proc(): bool =
testCond txInPayload(env.clMock.latestPayloadBuilt, shadow.txHash):
testCond txInPayload(
env.clMock.latestPayloadBuilt,
shadow.tx.get.tx.compute_tx_hash(
env.conf.networkParams.config.chainId)):
fatal "Payload built does not contain the transaction"
# Get the receipt
let receipt = env.engine.client.txReceipt(shadow.txHash)
let receipt = env.engine.client.txReceipt(
shadow.tx.get.tx.compute_tx_hash(
env.conf.networkParams.config.chainId))
testCond receipt.isOk:
fatal "Receipt not obtained after tx included in block"

View File

@ -9,7 +9,7 @@
# according to those terms.
import
eth/common,
eth/[common, common/transaction],
chronicles,
./engine_spec
@ -64,7 +64,8 @@ method execute(cs: BlockStatus, env: TestEnv): bool =
)
let tx = env.makeNextTx(tc)
shadow.txHash = tx.rlpHash
shadow.txHash = tx.tx.compute_tx_hash(
env.conf.networkParams.config.chainId)
let ok = env.sendTx(tx)
testCond ok:
fatal "Error trying to send transaction"

View File

@ -56,7 +56,7 @@ method execute(cs: SuggestedFeeRecipientTest, env: TestEnv): bool =
testCond env.clMock.produceSingleBlock(BlockProcessCallbacks())
# Calculate the fees and check that they match the balance of the fee recipient
let r = env.engine.client.latestBlock()
let r = env.engine.client.latestBlock(env.conf.networkParams.config.chainId)
testCond r.isOk:
error "cannot get latest header", msg=r.error
@ -72,14 +72,16 @@ method execute(cs: SuggestedFeeRecipientTest, env: TestEnv): bool =
var feeRecipientFees = 0.u256
for tx in blockIncluded.txs:
let effGasTip = tx.effectiveGasTip(blockIncluded.header.fee)
let effGasTip = tx.effectiveGasTip(
blockIncluded.header.fee.get(UInt256.zero))
let r = env.engine.client.txReceipt(tx.rlpHash)
testCond r.isOk:
fatal "unable to obtain receipt", msg=r.error
let receipt = r.get
feeRecipientFees = feeRecipientFees + effGasTip.u256 * receipt.gasUsed.u256
feeRecipientFees =
feeRecipientFees + effGasTip.uint64.u256 * receipt.gasUsed.u256
var s = env.engine.client.balanceAt(feeRecipient)

View File

@ -11,7 +11,7 @@
import
std/[times, json, strutils],
stew/byteutils,
eth/[common, common/eth_types, rlp], chronos,
eth/[common, common/eth_types, common/transaction], chronos,
json_rpc/[rpcclient, errors, jsonmarshal],
../../../nimbus/beacon/web3_eth_conv,
./types
@ -206,8 +206,9 @@ proc newPayloadV4*(client: RpcClient,
proc collectBlobHashes(list: openArray[Web3Tx]): seq[Web3Hash] =
for w3tx in list:
let tx = ethTx(w3tx)
for h in tx.versionedHashes:
result.add w3Hash(h)
if tx.payload.blob_versioned_hashes.isSome:
for h in tx.payload.blob_versioned_hashes.unsafeGet:
result.add w3Hash(h)
proc newPayload*(client: RpcClient,
payload: ExecutionPayload,
@ -308,30 +309,95 @@ func vHashes(x: Option[seq[Web3Hash]]): seq[common.Hash256] =
if x.isNone: return
else: ethHashes(x.get)
proc toTransaction(tx: TransactionObject): Transaction =
common.Transaction(
txType : tx.`type`.get(0.Web3Quantity).TxType,
chainId : tx.chainId.get(0.Web3Quantity).ChainId,
nonce : tx.nonce.AccountNonce,
gasPrice : tx.gasPrice.GasInt,
maxPriorityFee : tx.maxPriorityFeePerGas.get(0.Web3Quantity).GasInt,
maxFee : tx.maxFeePerGas.get(0.Web3Quantity).GasInt,
gasLimit : tx.gas.GasInt,
to : ethAddr tx.to,
value : tx.value,
payload : tx.input,
accessList : ethAccessList(tx.accessList),
maxFeePerBlobGas: tx.maxFeePerBlobGas.get(0.u256),
versionedHashes : vHashes(tx.blobVersionedHashes),
V : tx.v.int64,
R : tx.r,
S : tx.s,
)
proc toTransaction(tx: TransactionObject, chain_id: ChainId): Transaction =
var
payload: TransactionPayload
gasPrice: Opt[UInt256]
txChainId: Opt[ChainId]
v: UInt256
r: UInt256
s: UInt256
if tx.`type`.isSome:
payload.tx_type.ok tx.`type`.get.TxType
if tx.chainId.isSome:
txChainId.ok tx.chainId.get.ChainId
payload.nonce = tx.nonce.AccountNonce
payload.max_fee_per_gas = distinctBase(tx.gasPrice).u256
if tx.maxPriorityFeePerGas.isSome:
payload.max_priority_fee_per_gas.ok(
distinctBase(tx.maxPriorityFeePerGas.get).u256)
if tx.maxFeePerGas.isSome:
gasPrice.ok distinctBase(tx.maxFeePerGas.get).u256
payload.gas = distinctBase(tx.gas)
if tx.to.isSome:
payload.to.ok(ethAddr(tx.to).get)
payload.value = tx.value
if tx.input.len > payload.input.maxLen:
raise (ref ValueError)(msg:
"Input cannot fit " & $tx.input.len & " bytes")
payload.input = List[byte, Limit MAX_CALLDATA_SIZE].init tx.input
if tx.accessList.isSome:
payload.access_list.ok ethAccessList(tx.accessList)
if tx.maxFeePerBlobGas.isSome:
payload.max_fee_per_blob_gas.ok tx.maxFeePerBlobGas.get
if tx.blobVersionedHashes.isSome:
if tx.blobVersionedHashes.get.len > MAX_BLOB_COMMITMENTS_PER_BLOCK:
raise (ref ValueError)(msg:
"Access list cannot fit " & $tx.blobVersionedHashes.get.len & " bytes")
payload.blob_versioned_hashes.ok(
List[eth_types.VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init vHashes(tx.blobVersionedHashes))
v = distinctBase(tx.v).u256
r = tx.r
s = tx.s
if gasPrice.get(payload.max_fee_per_gas) != payload.max_fee_per_gas:
raise (ref ValueError)(msg: "`gasPrice` and `maxFeePerGas` don't match")
if payload.tx_type.get(TxLegacy) != TxLegacy and txChainId.isNone:
raise (ref ValueError)(msg: "`chainId` is required")
if payload.tx_type == Opt.some(TxLegacy) and txChainId.isNone:
payload.tx_type.reset()
if txChainId.get(chain_id) != chain_id:
raise (ref ValueError)(msg: "Unsupported `chainId`")
if r >= SECP256K1N:
raise (ref ValueError)(msg: "Invalid `r`")
if s < UInt256.one or s >= SECP256K1N:
raise (ref ValueError)(msg: "Invalid `s`")
let anyTx = AnyTransactionPayload.fromOneOfBase(payload).valueOr:
raise (ref ValueError)(msg: "Invalid combination of fields")
withTxPayloadVariant(anyTx):
let y_parity =
when txKind == TransactionKind.Replayable:
if v == 27.u256:
false
elif v == 28.u256:
true
else:
raise (ref ValueError)(msg: "Invalid `v`")
elif txKind == TransactionKind.Legacy:
let
res = v.isEven
expected_v =
distinctBase(chain_id).u256 * 2 + (if res: 36.u256 else: 35.u256)
if v != expected_v:
raise (ref ValueError)(msg: "Invalid `v`")
res
else:
if v > UInt256.one:
raise (ref ValueError)(msg: "Invalid `v`")
v.isOdd
var signature: TransactionSignature
signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s)
signature.from_address = ecdsa_recover_from_address(
signature.ecdsa_signature,
txPayloadVariant.compute_sig_hash(chain_id)).valueOr:
raise (ref ValueError)(msg: "Cannot compute `from` address")
Transaction(payload: payload, signature: signature)
proc toTransactions*(txs: openArray[TxOrHash]): seq[Transaction] =
proc toTransactions*(
txs: openArray[TxOrHash], chain_id: ChainId): seq[Transaction] =
for x in txs:
doAssert x.kind == tohTx
result.add toTransaction(x.tx)
result.add toTransaction(x.tx, chain_id)
proc toWithdrawal(wd: WithdrawalObject): Withdrawal =
Withdrawal(
@ -493,14 +559,15 @@ proc latestHeader*(client: RpcClient): Result[common.BlockHeader, string] =
return err("failed to get latest blockHeader")
return ok(res.toBlockHeader)
proc latestBlock*(client: RpcClient): Result[common.EthBlock, string] =
proc latestBlock*(
client: RpcClient, chainId: ChainId): Result[common.EthBlock, string] =
wrapTry:
let res = waitFor client.eth_getBlockByNumber(blockId("latest"), true)
if res.isNil:
return err("failed to get latest blockHeader")
let output = EthBlock(
header: toBlockHeader(res),
txs: toTransactions(res.transactions),
txs: toTransactions(res.transactions, chainId),
withdrawals: toWithdrawals(res.withdrawals),
)
return ok(output)
@ -513,11 +580,13 @@ proc namedHeader*(client: RpcClient, name: string): Result[common.BlockHeader, s
return ok(res.toBlockHeader)
proc sendTransaction*(
client: RpcClient, tx: common.PooledTransaction): Result[void, string] =
client: RpcClient,
tx: common.PooledTransaction,
chainId: ChainId): Result[void, string] =
wrapTry:
let encodedTx = rlp.encode(tx)
let encodedTx = tx.toBytes(chainId)
let res = waitFor client.eth_sendRawTransaction(encodedTx)
let txHash = rlpHash(tx)
let txHash = tx.tx.compute_tx_hash(chainId)
let getHash = ethHash res
if txHash != getHash:
return err("sendTransaction: tx hash mismatch")
@ -607,9 +676,10 @@ createRpcSigsFromNim(RpcClient):
proc debugPrevRandaoTransaction*(
client: RpcClient,
tx: PooledTransaction,
expectedPrevRandao: Hash256): Result[void, string] =
expectedPrevRandao: Hash256,
chain_id: ChainId): Result[void, string] =
wrapTry:
let hash = w3Hash tx.rlpHash
let hash = tx.tx.compute_tx_hash(chain_id).data.TxHash
# we only interested in stack, disable all other elems
let opts = TraceOpts(
disableStorage: true,

View File

@ -58,7 +58,7 @@ const
proc makeCom*(conf: NimbusConf): CommonRef =
CommonRef.new(
newCoreDbRef LegacyDbMemory,
newCoreDbRef(LegacyDbMemory, conf.networkParams.config.chainId),
conf.chainDbMode == ChainDbMode.Prune,
conf.networkId,
conf.networkParams

View File

@ -32,7 +32,7 @@ export
type
TestEnv* = ref object
conf : NimbusConf
conf* : NimbusConf
chainFile : string
enableAuth: bool
port : int
@ -150,7 +150,7 @@ proc sendTx*(env: TestEnv, eng: EngineEnv, tc: BigInitcodeTx, nonce: AccountNonc
proc sendTxs*(
env: TestEnv, eng: EngineEnv, txs: openArray[PooledTransaction]): bool =
for tx in txs:
if not sendTx(eng.client, tx):
if not sendTx(eng.client, tx, env.conf.networkParams.config.chainId):
return false
true
@ -168,7 +168,7 @@ proc sendTx*(env: TestEnv, tc: BigInitcodeTx, nonce: AccountNonce): bool =
proc sendTx*(env: TestEnv, tx: PooledTransaction): bool =
let client = env.engine.client
sendTx(client, tx)
sendTx(client, tx, env.conf.networkParams.config.chainId)
proc sendTx*(
env: TestEnv,
@ -195,7 +195,8 @@ proc customizeTransaction*(env: TestEnv,
acc: TestAccount,
baseTx: Transaction,
custTx: CustomTransactionData): Transaction =
env.sender.customizeTransaction(acc, baseTx, custTx)
env.sender.customizeTransaction(
acc, baseTx, custTx, env.conf.networkParams.config.chainId)
proc generateInvalidPayload*(env: TestEnv,
data: ExecutableData,

View File

@ -10,8 +10,9 @@
import
std/[tables],
eth/keys,
eth/[common/transaction, keys],
stew/endians2,
stint,
nimcrypto/sha2,
chronicles,
./engine_client,
@ -50,7 +51,7 @@ type
accounts: seq[TestAccount]
nonceMap: Table[EthAddress, uint64]
txSent : int
chainId : ChainID
chainId*: ChainID
MakeTxParams* = object
chainId*: ChainID
@ -142,29 +143,34 @@ proc makeTxOfType(params: MakeTxParams, tc: BaseTx): PooledTransaction =
of TxLegacy:
PooledTransaction(
tx: Transaction(
txType : TxLegacy,
nonce : params.nonce,
to : tc.recipient,
value : tc.amount,
gasLimit: tc.gasLimit,
gasPrice: gasPrice,
payload : tc.payload
)
)
payload: TransactionPayload(
nonce: params.nonce,
to:
if tc.recipient.isSome:
Opt.some(tc.recipient.get)
else:
Opt.none(EthAddress),
value: tc.amount,
gas: tc.gasLimit.uint64,
max_fee_per_gas: gasPrice.uint64.u256,
input: List[byte, Limit MAX_CALLDATA_SIZE].init tc.payload)))
of TxEip1559:
PooledTransaction(
tx: Transaction(
txType : TxEIP1559,
nonce : params.nonce,
gasLimit: tc.gasLimit,
maxFee : gasFeeCap,
maxPriorityFee: gasTipCap,
to : tc.recipient,
value : tc.amount,
payload : tc.payload,
chainId : params.chainId
)
)
payload: TransactionPayload(
tx_type: Opt.some TxEip1559,
nonce: params.nonce,
gas: tc.gasLimit.uint64,
max_fee_per_gas: gasPrice.uint64.u256,
max_priority_fee_per_gas: Opt.some(gasTipCap.uint64.u256),
to:
if tc.recipient.isSome:
Opt.some(tc.recipient.get)
else:
Opt.none(EthAddress),
value: tc.amount,
input: List[byte, Limit MAX_CALLDATA_SIZE].init tc.payload)))
of TxEip4844:
doAssert(tc.recipient.isSome, "recipient must be some")
let
@ -179,23 +185,33 @@ proc makeTxOfType(params: MakeTxParams, tc: BaseTx): PooledTransaction =
PooledTransaction(
tx: Transaction(
txType : TxEIP4844,
nonce : params.nonce,
chainId : params.chainId,
maxFee : gasFeeCap,
maxPriorityFee: gasTipCap,
gasLimit: tc.gasLimit,
to : tc.recipient,
value : tc.amount,
payload : tc.payload,
#AccessList: tc.AccessList,
maxFeePerBlobGas: blobFeeCap,
versionedHashes: system.move(blobData.hashes),
),
networkPayload: NetworkPayload(
blobs: system.move(blobData.blobs),
commitments: system.move(blobData.commitments),
proofs: system.move(blobData.proofs),
payload: TransactionPayload(
tx_type: Opt.some TxEip4844,
nonce: params.nonce,
max_fee_per_gas: gasPrice.uint64.u256,
max_priority_fee_per_gas: Opt.some(gasTipCap.uint64.u256),
gas: tc.gasLimit.uint64,
to:
if tc.recipient.isSome:
Opt.some(tc.recipient.get)
else:
Opt.none(EthAddress),
value: tc.amount,
input: List[byte, Limit MAX_CALLDATA_SIZE].init tc.payload,
max_fee_per_blob_gas: Opt.some(blobFeeCap),
blob_versioned_hashes: Opt.some(
List[eth_types.VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(system.move(blobData.hashes))))),
blob_data: Opt.some NetworkPayload(
blobs:
List[NetworkBlob, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(system.move(blobData.blobs)),
commitments:
List[eth_types.KzgCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(system.move(blobData.commitments)),
proofs:
List[eth_types.KzgProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(system.move(blobData.proofs)),
)
)
else:
@ -205,8 +221,8 @@ 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),
networkPayload: tx.networkPayload)
tx: signTransaction(tx.tx.payload, params.key, params.chainId),
blob_data: tx.blob_data)
proc makeTx(params: MakeTxParams, tc: BigInitcodeTx): PooledTransaction =
var tx = tc
@ -257,7 +273,7 @@ proc makeNextTx*(sender: TxSender, tc: BaseTx): PooledTransaction =
proc sendNextTx*(sender: TxSender, client: RpcClient, tc: BaseTx): bool =
let tx = sender.makeNextTx(tc)
let rr = client.sendTransaction(tx)
let rr = client.sendTransaction(tx, sender.chainId)
if rr.isErr:
error "sendNextTx: Unable to send transaction", msg=rr.error
return false
@ -275,7 +291,7 @@ proc sendTx*(sender: TxSender, client: RpcClient, tc: BaseTx, nonce: AccountNonc
)
tx = params.makeTx(tc)
let rr = client.sendTransaction(tx)
let rr = client.sendTransaction(tx, sender.chainId)
if rr.isErr:
error "sendTx: Unable to send transaction", msg=rr.error
return false
@ -293,7 +309,7 @@ proc sendTx*(sender: TxSender, client: RpcClient, tc: BigInitcodeTx, nonce: Acco
)
tx = params.makeTx(tc)
let rr = client.sendTransaction(tx)
let rr = client.sendTransaction(tx, sender.chainId)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
@ -301,8 +317,8 @@ proc sendTx*(sender: TxSender, client: RpcClient, tc: BigInitcodeTx, nonce: Acco
inc sender.txSent
return true
proc sendTx*(client: RpcClient, tx: PooledTransaction): bool =
let rr = client.sendTransaction(tx)
proc sendTx*(client: RpcClient, tx: PooledTransaction, chainId: ChainId): bool =
let rr = client.sendTransaction(tx, chainId)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
@ -320,27 +336,36 @@ proc makeTx*(params: MakeTxParams, tc: BlobTx): PooledTransaction =
else: gasTipPrice
# Collect fields for transaction
let unsignedTx = Transaction(
txType : TxEip4844,
chainId : params.chainId,
nonce : params.nonce,
maxPriorityFee: gasTipCap,
maxFee : gasFeeCap,
gasLimit : tc.gasLimit,
to : tc.recipient,
value : tc.amount,
payload : tc.payload,
maxFeePerBlobGas: tc.blobGasFee,
versionedHashes: data.hashes,
)
let unsignedTx = TransactionPayload(
tx_type: Opt.some TxEip4844,
nonce: params.nonce,
max_priority_fee_per_gas: Opt.some(gasTipCap.uint64.u256),
max_fee_per_gas: gasFeeCap.uint64.u256,
gas: tc.gasLimit.uint64,
to:
if tc.recipient.isSome:
Opt.some(tc.recipient.get)
else:
Opt.none(EthAddress),
value: tc.amount,
input: List[byte, Limit MAX_CALLDATA_SIZE].init tc.payload,
max_fee_per_blob_gas: Opt.some(tc.blobGasFee),
blob_versioned_hashes: Opt.some(
List[eth_types.VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(data.hashes)))
PooledTransaction(
tx: signTransaction(unsignedTx, params.key, params.chainId, eip155 = true),
networkPayload: NetworkPayload(
blobs : data.blobs,
commitments: data.commitments,
proofs : data.proofs,
),
tx: signTransaction(unsignedTx, params.key, params.chainId),
blob_data: Opt.some NetworkPayload(
blobs:
List[NetworkBlob, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(data.blobs),
commitments:
List[eth_types.KzgCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(data.commitments),
proofs:
List[eth_types.KzgProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(data.proofs),
)
)
proc getAccount*(sender: TxSender, idx: int): TestAccount =
@ -359,7 +384,7 @@ proc sendTx*(
)
tx = params.makeTx(tc)
let rr = client.sendTransaction(tx)
let rr = client.sendTransaction(tx, params.chainId)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return err()
@ -380,7 +405,7 @@ proc replaceTx*(
)
tx = params.makeTx(tc)
let rr = client.sendTransaction(tx)
let rr = client.sendTransaction(tx, params.chainId)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return err()
@ -404,49 +429,86 @@ proc makeTx*(
proc customizeTransaction*(sender: TxSender,
acc: TestAccount,
baseTx: Transaction,
custTx: CustomTransactionData): Transaction =
custTx: CustomTransactionData,
chainId: ChainId): Transaction =
# Create a modified transaction base, from the base transaction and custTx mix
var modTx = baseTx
if custTx.nonce.isSome:
modTx.nonce = custTx.nonce.get.AccountNonce
modTx.payload.nonce = custTx.nonce.get.AccountNonce
if custTx.gasPriceOrGasFeeCap.isSome:
modTx.gasPrice = custTx.gasPriceOrGasFeeCap.get.GasInt
modTx.payload.max_fee_per_gas = custTx.gasPriceOrGasFeeCap.get.u256
if custTx.gas.isSome:
modTx.gasLimit = custTx.gas.get.GasInt
modTx.payload.gas = custTx.gas.get.uint64
if custTx.to.isSome:
modTx.to = custTx.to
modTx.payload.to.ok custTx.to.get
if custTx.value.isSome:
modTx.value = custTx.value.get
modTx.payload.value = custTx.value.get
if custTx.data.isSome:
modTx.payload = custTx.data.get
modTx.payload.input =
List[byte, Limit MAX_CALLDATA_SIZE].init(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 baseTx.txType in {TxEip1559, TxEip4844}:
let custChainId =
if custTx.chainId.isSome:
modTx.chainId = custTx.chainId.get
custTx.chainId.get
else:
chainId
if baseTx.payload.tx_type.get(TxLegacy) in {TxEip1559, TxEip4844}:
if custTx.gasPriceOrGasFeeCap.isSome:
modTx.maxFee = custTx.gasPriceOrGasFeeCap.get.GasInt
modTx.payload.max_fee_per_gas = custTx.gasPriceOrGasFeeCap.get.u256
if custTx.gasTipCap.isSome:
modTx.maxPriorityFee = custTx.gasTipCap.get.GasInt
modTx.payload.max_priority_fee_per_gas.ok custTx.gasTipCap.get.u256
if baseTx.txType == TxEip4844:
if modTx.to.isNone:
if baseTx.payload.tx_type.get(TxLegacy) == TxEip4844:
if modTx.payload.to.isNone:
var address: EthAddress
modTx.to = some(address)
modTx.payload.to.ok(address)
if custTx.signature.isSome:
let
signature = custTx.signature.get
v = signature.V.u256
r = signature.R
s = signature.S
anyTx = AnyTransactionPayload.fromOneOfBase(modTx.payload).valueOr:
raise (ref ValueError)(msg: "Invalid combination of fields")
return withTxPayloadVariant(anyTx):
let y_parity =
when txKind == TransactionKind.Replayable:
if v == 27.u256:
false
elif v == 28.u256:
true
else:
raise (ref ValueError)(msg: "Invalid `v`")
elif txKind == TransactionKind.Legacy:
let
res = v.isEven
expected_v =
distinctBase(custChainId).u256 * 2 +
(if res: 36.u256 else: 35.u256)
if v != expected_v:
raise (ref ValueError)(msg: "Invalid `v`")
res
else:
if v > UInt256.one:
raise (ref ValueError)(msg: "Invalid `v`")
v.isOdd
var signature: TransactionSignature
signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s)
signature.from_address = ecdsa_recover_from_address(
signature.ecdsa_signature,
txPayloadVariant.compute_sig_hash(custChainId)).valueOr:
raise (ref ValueError)(msg: "Cannot compute `from` address")
Transaction(payload: modTx.payload, signature: signature)
if custTx.signature.isNone:
return signTransaction(modTx, acc.key, modTx.chainId, eip155 = true)
return signTransaction(modTx.payload, acc.key, custChainId)
return modTx

View File

@ -26,7 +26,7 @@ proc execute*(ws: BlockValueSpec, env: TestEnv): bool =
testCond WDBaseSpec(ws).execute(env)
# Get the latest block and the transactions included
let b = env.client.latestBlock()
let b = env.client.latestBlock(env.conf.networkParams.config.chainId)
b.expectNoError()
let blk = b.get

View File

@ -12,7 +12,7 @@ import
std/typetraits,
chronos,
chronicles,
eth/common/eth_types_rlp,
eth/common/[eth_types_rlp, transaction],
./wd_base_spec,
../test_env,
../engine_client,
@ -87,7 +87,8 @@ proc execute*(ws: MaxInitcodeSizeSpec, env: TestEnv): bool =
testCond not env.sendTx(tx):
error "Client accepted tx exceeding the MAX_INITCODE_SIZE"
let res = env.client.txByHash(rlpHash(tx))
let res = env.client.txByHash(
tx.tx.compute_tx_hash(env.conf.networkParams.config.chainId))
testCond res.isErr:
error "Invalid tx was not unknown to the client"
@ -102,7 +103,7 @@ proc execute*(ws: MaxInitcodeSizeSpec, env: TestEnv): bool =
return true
,
onGetPayload: proc(): bool =
let validTxBytes = rlp.encode(validTx)
let validTxBytes = validTx.toBytes(env.conf.networkParams.config.chainId)
testCond env.clMock.latestPayloadBuilt.transactions.len == 1:
error "Client did not include valid tx with MAX_INITCODE_SIZE"

View File

@ -78,7 +78,8 @@ proc main() =
conf = makeConfig(@["--custom-network:" & genesisFile])
ethCtx = newEthContext()
ethNode = setupEthNode(conf, ethCtx, eth)
com = CommonRef.new(newCoreDbRef LegacyDbMemory,
com = CommonRef.new(
newCoreDbRef(LegacyDbMemory, conf.networkParams.config.chainId),
pruneTrie = false,
conf.networkId,
conf.networkParams

View File

@ -8,7 +8,7 @@
# those terms.
import
eth/[common, rlp],
eth/common,
chronos, stint,
json_rpc/[rpcclient],
../../../nimbus/transaction,
@ -19,8 +19,10 @@ import
export eth_api
proc sendTransaction*(
client: RpcClient, tx: PooledTransaction): Future[bool] {.async.} =
let data = rlp.encode(tx)
client: RpcClient,
tx: PooledTransaction,
chainId: ChainId): Future[bool] {.async.} =
let data = tx.toBytes(chainId)
let txHash = keccakHash(data)
let hex = await client.eth_sendRawTransaction(data)
let decodedHash = ethHash(hex)

View File

@ -9,7 +9,7 @@
import
std/strutils,
eth/[common],
eth/[common, common/transaction],
stew/byteutils,
stint,
chronos,
@ -88,11 +88,11 @@ proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} =
let tx = vault.signTx(sourceAddr, sourceNonce, targetAddr, amount, gasLimit, gasPrice)
inc sourceNonce
let txHash = rlpHash(tx)
let txHash = tx.tx.compute_tx_hash(vault.chainId)
echo "BalanceAt: send $1 wei from 0x$2 to 0x$3 in 0x$4" % [
$tx.tx.value, sourceAddr.toHex, targetAddr.toHex, txHash.data.toHex]
$tx.tx.payload.value, sourceAddr.toHex, targetAddr.toHex, txHash.data.toHex]
let ok = await client.sendTransaction(tx)
let ok = await client.sendTransaction(tx, vault.chainId)
if not ok:
echo "failed to send transaction"
return TestStatus.Failed
@ -118,7 +118,8 @@ proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} =
# expected balance is previous balance - tx amount - tx fee (gasUsed * gasPrice)
let exp =
sourceAddressBalanceBefore - amount - (gasUsed * tx.tx.gasPrice).u256
sourceAddressBalanceBefore - amount -
gasUsed.u256 * tx.tx.payload.max_fee_per_gas
if exp != accountBalanceAfter:
echo "Expected sender account to have a balance of $1, got $2" % [$exp, $accountBalanceAfter]
@ -126,7 +127,7 @@ proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} =
if balanceTargetAccountAfter != amount:
echo "Expected new account to have a balance of $1, got $2" % [
$tx.tx.value, $balanceTargetAccountAfter]
$tx.tx.payload.value, $balanceTargetAccountAfter]
return TestStatus.Failed
# ensure nonce is incremented by 1

View File

@ -76,7 +76,8 @@ proc setupEnv*(): TestEnv =
let
ethCtx = newEthContext()
ethNode = setupEthNode(conf, ethCtx, eth)
com = CommonRef.new(newCoreDbRef LegacyDbMemory,
com = CommonRef.new(
newCoreDbRef(LegacyDbMemory, conf.networkParams.config.chainId),
conf.chainDbMode == ChainDbMode.Prune,
conf.networkId,
conf.networkParams

View File

@ -42,7 +42,7 @@ type
accounts: Table[EthAddress, PrivateKey]
rng: ref HmacDrbgContext
chainId: ChainID
chainId*: ChainID
gasPrice: GasInt
vaultKey: PrivateKey
client: RpcClient
@ -68,57 +68,49 @@ proc nextNonce*(v: Vault): AccountNonce =
inc(v.nonce)
nonce
proc sendSome(address: EthAddress, amount: UInt256): seq[byte] =
proc sendSome(
address: EthAddress, amount: UInt256): List[byte, Limit MAX_CALLDATA_SIZE] =
const padding = repeat('\0', 12).toBytes
# makeshift contract ABI construction
# https://docs.soliditylang.org/en/develop/abi-spec.html
let h = keccakHash("sendSome(address,uint256)".toBytes)
result.add h.data[0..3] # first 4 bytes of hash
result.add padding # left pad address
result.add address
result.add amount.toBytesBE
doAssert result.add h.data[0..3] # first 4 bytes of hash
doAssert result.add padding # left pad address
doAssert result.add address
doAssert result.add amount.toBytesBE
doAssert(result.len == 68) # 4 + 32 + 32
proc makeFundingTx*(
v: Vault, recipient: EthAddress, amount: UInt256): PooledTransaction =
let unsignedTx = TransactionPayload(
nonce: v.nextNonce(),
max_fee_per_gas: v.gasPrice.uint64.u256,
gas: 75000,
to: Opt.some(predeployedVaultAddr),
value: 0.u256,
input: sendSome(recipient, amount),
tx_type: Opt.some TxLegacy)
PooledTransaction(tx: signTransaction(unsignedTx, v.vaultKey, v.chainId))
proc signTx*(
v: Vault,
sender: EthAddress,
nonce: AccountNonce,
recipient: EthAddress,
amount: UInt256,
gasLimit, gasPrice: GasInt,
payload = List[byte, Limit MAX_CALLDATA_SIZE] @[]): PooledTransaction =
let
unsignedTx = Transaction(
txType : TxLegacy,
chainId : v.chainId,
nonce : v.nextNonce(),
gasPrice: v.gasPrice,
gasLimit: GasInt(75000),
to : some(predeployedVaultAddr),
value : 0.u256,
payload : sendSome(recipient, amount)
)
PooledTransaction(
tx: signTransaction(unsignedTx, v.vaultKey, v.chainId, eip155 = true))
proc signTx*(v: Vault,
sender: EthAddress,
nonce: AccountNonce,
recipient: EthAddress,
amount: UInt256,
gasLimit, gasPrice: GasInt,
payload: seq[byte] = @[]): PooledTransaction =
let
unsignedTx = Transaction(
txType : TxLegacy,
chainId : v.chainId,
nonce : nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to : some(recipient),
value : amount,
payload : payload
)
let key = v.accounts[sender]
PooledTransaction(
tx: signTransaction(unsignedTx, key, v.chainId, eip155 = true))
unsignedTx = TransactionPayload(
nonce: nonce,
max_fee_per_gas: gasPrice.uint64.u256,
gas: gasLimit.uint64,
to: Opt.some(recipient),
value: amount,
input: payload,
tx_type: Opt.some TxLegacy)
key = v.accounts[sender]
PooledTransaction(tx: signTransaction(unsignedTx, key, v.chainId))
# createAccount creates a new account that is funded from the vault contract.
# It will panic when the account could not be created and funded.
@ -127,7 +119,7 @@ proc createAccount*(v: Vault, amount: UInt256): Future[EthAddress] {.async.} =
# order the vault to send some ether
let tx = v.makeFundingTx(address, amount)
let res = await v.client.sendTransaction(tx)
let res = await v.client.sendTransaction(tx, v.chainId)
if not res:
raise newException(ValueError, "unable to send funding transaction")
@ -147,5 +139,5 @@ proc createAccount*(v: Vault, amount: UInt256): Future[EthAddress] {.async.} =
let period = chronos.seconds(1)
await sleepAsync(period)
let txHash = tx.rlpHash().data.toHex
let txHash = tx.tx.compute_tx_hash(v.chainId).data.toHex
raise newException(ValueError, "could not fund account $2 in transaction $2" % [address.toHex, txHash])

View File

@ -8,7 +8,7 @@
# those terms.
import
eth/common,
eth/[common, common/transaction],
stew/results,
../web3_eth_conv,
../beacon_engine,
@ -20,11 +20,15 @@ import
{.push gcsafe, raises:[CatchableError].}
func validateVersionedHashed(payload: ExecutionPayload,
expected: openArray[Web3Hash]): bool =
expected: openArray[Web3Hash],
chainId: ChainId): bool =
var versionedHashes: seq[common.Hash256]
for x in payload.transactions:
let tx = rlp.decode(distinctBase(x), Transaction)
versionedHashes.add tx.versionedHashes
let tx = Transaction.fromBytes(distinctBase(x), chainId).valueOr:
raise (ref MalformedRlpError)(msg: "Invalid transaction in payload")
if tx.payload.blob_versioned_hashes.isSome:
versionedHashes.add distinctBase(
tx.payload.blob_versioned_hashes.unsafeGet)
if versionedHashes.len != expected.len:
return false
@ -125,7 +129,7 @@ proc newPayload*(ben: BeaconEngineRef,
if versionedHashes.isNone:
raise invalidParams("newPayload" & $apiVersion &
" expect blobVersionedHashes but got none")
if not validateVersionedHashed(payload, versionedHashes.get):
if not validateVersionedHashed(payload, versionedHashes.get, com.chainId):
return invalidStatus(header.parentHash, "invalid blob versionedHashes")
let blockHash = ethHash payload.blockHash

View File

@ -157,16 +157,18 @@ func ethTxs*(list: openArray[Web3Tx]):
for x in list:
result.add ethTx(x)
func storageKeys(list: seq[FixedBytes[32]]): seq[StorageKey] =
func storageKeys(list: seq[FixedBytes[32]]): common.StorageKeys =
for x in list:
result.add StorageKey(x)
let ok = result.add distinctBase(x)
doAssert ok, "StorageKeys capacity exceeded"
func ethAccessList*(list: openArray[AccessTuple]): common.AccessList =
for x in list:
result.add common.AccessPair(
let ok = result.add common.AccessPair(
address : ethAddr x.address,
storageKeys: storageKeys x.storageKeys,
)
doAssert ok, "AccessList capacity exceeded"
func ethAccessList*(x: Option[seq[AccessTuple]]): common.AccessList =
if x.isSome:
@ -286,10 +288,10 @@ func w3Txs*(list: openArray[common.Transaction]): seq[Web3Tx] =
proc w3AccessTuple*(ac: AccessPair): AccessTuple =
AccessTuple(
address: w3Addr ac.address,
storageKeys: w3Hash(ac.storageKeys)
storageKeys: w3Hash(distinctBase(ac.storage_keys))
)
proc w3AccessList*(list: openArray[AccessPair]): seq[AccessTuple] =
proc w3AccessList*(list: common.AccessList): seq[AccessTuple] =
result = newSeqOfCap[AccessTuple](list.len)
for x in list:
result.add w3AccessTuple(x)

View File

@ -190,7 +190,7 @@ proc init(com : CommonRef,
time: some(genesis.timestamp)
))
com.genesisHeader = toGenesisHeader(genesis,
com.currentFork, com.db, com.ldgType)
com.currentFork, config.chainId, com.db, com.ldgType)
com.setForkId(com.genesisHeader)
com.pos.timestamp = genesis.timestamp
else:

View File

@ -225,6 +225,7 @@ proc toGenesisHeader*(
proc toGenesisHeader*(
genesis: Genesis;
fork: HardFork;
chainId: ChainId;
db = CoreDbRef(nil);
ledgerType = GenesisLedgerTypeDefault;
): BlockHeader
@ -232,7 +233,7 @@ proc toGenesisHeader*(
## Generate the genesis block header from the `genesis` and `config`
## argument value.
let
db = if db.isNil: newCoreDbRef LegacyDbMemory else: db
db = if db.isNil: newCoreDbRef LegacyDbMemory, chainId else: db
sdb = newStateDB(db, pruneTrie = true, ledgerType)
toGenesisHeader(genesis, sdb, fork)
@ -246,7 +247,7 @@ proc toGenesisHeader*(
## argument value.
let map = toForkTransitionTable(params.config)
let fork = map.toHardFork(forkDeterminationInfo(0.toBlockNumber, params.genesis.timestamp))
toGenesisHeader(params.genesis, fork, db, ledgerType)
toGenesisHeader(params.genesis, fork, params.config.chainId, db, ledgerType)
# ------------------------------------------------------------------------------
# End

View File

@ -83,9 +83,6 @@ const
DEFAULT_RPC_GAS_CAP* = 50_000_000.GasInt
# EIP-4844 constants
MAX_CALLDATA_SIZE* = 1 shl 24 # 2^24
MAX_ACCESS_LIST_SIZE* = 1 shl 24 # 2^24
MAX_ACCESS_LIST_STORAGE_KEYS* = 1 shl 24 # 2^24
MAX_TX_WRAP_COMMITMENTS* = 1 shl 12 # 2^12
VERSIONED_HASH_VERSION_KZG* = 0x01.byte
FIELD_ELEMENTS_PER_BLOB* = 4096

View File

@ -9,7 +9,7 @@
# according to those terms.
import
std/[os, strutils],
std/[os, strutils, typetraits],
nimcrypto/sha2,
kzg4844/kzg_ex as kzg,
stew/results,
@ -108,7 +108,9 @@ func fakeExponential*(factor, numerator, denominator: UInt256): UInt256 =
output div denominator
proc getTotalBlobGas*(tx: Transaction): uint64 =
GAS_PER_BLOB * tx.versionedHashes.len.uint64
let vhs = tx.payload.blob_versioned_hashes.valueOr:
return 0
GAS_PER_BLOB * vhs.len.uint64
proc getTotalBlobGas*(versionedHashesLen: int): uint64 =
GAS_PER_BLOB * versionedHashesLen.uint64
@ -169,38 +171,44 @@ func validateEip4844Header*(
proc validateBlobTransactionWrapper*(tx: PooledTransaction):
Result[void, string] {.raises: [].} =
if tx.networkPayload.isNil:
if tx.tx.payload.blob_versioned_hashes.isNone:
if tx.blob_data.isSome:
return err("tx wrapper contains unexpected blobs")
return ok()
if tx.blob_data.isNone:
return err("tx wrapper is none")
template blob_versioned_hashes: untyped =
tx.tx.payload.blob_versioned_hashes.unsafeGet
template blob_data: untyped =
tx.blob_data.unsafeGet
# note: assert blobs are not malformatted
let goodFormatted = tx.tx.versionedHashes.len ==
tx.networkPayload.commitments.len and
tx.tx.versionedHashes.len ==
tx.networkPayload.blobs.len and
tx.tx.versionedHashes.len ==
tx.networkPayload.proofs.len
let goodFormatted = blob_versioned_hashes.len ==
blob_data.commitments.len and
blob_versioned_hashes.len ==
blob_data.blobs.len and
blob_versioned_hashes.len ==
blob_data.proofs.len
if not goodFormatted:
return err("tx wrapper is ill formatted")
# Verify that commitments match the blobs by checking the KZG proof
let res = kzg.verifyBlobKzgProofBatch(tx.networkPayload.blobs,
tx.networkPayload.commitments, tx.networkPayload.proofs)
if res.isErr:
return err(res.error)
# Actual verification result
if not res.get():
if not(? kzg.verifyBlobKzgProofBatch(
distinctBase(blob_data.blobs),
distinctBase(blob_data.commitments),
distinctBase(blob_data.proofs))):
return err("Failed to verify network payload of a transaction")
# Now that all commitments have been verified, check that versionedHashes matches the commitments
for i in 0 ..< tx.tx.versionedHashes.len:
# Now that all commitments have been verified, check that versionedHashes
# matches the commitments
for i in 0 ..< blob_versioned_hashes.len:
# this additional check also done in tx validation
if tx.tx.versionedHashes[i].data[0] != VERSIONED_HASH_VERSION_KZG:
if blob_versioned_hashes[i].data[0] != VERSIONED_HASH_VERSION_KZG:
return err("wrong kzg version in versioned hash at index " & $i)
if tx.tx.versionedHashes[i] !=
kzgToVersionedHash(tx.networkPayload.commitments[i]):
if blob_versioned_hashes[i] != kzgToVersionedHash(blob_data.commitments[i]):
return err("tx versioned hash not match commitments at index " & $i)
ok()

View File

@ -42,7 +42,8 @@ proc processTransactions*(vmState: BaseVMState;
let rc = vmState.processTransaction(tx, sender, header)
if rc.isErr:
return err("Error processing tx with index " & $(txIndex) & ":" & rc.error)
vmState.receipts[txIndex] = vmState.makeReceipt(tx.txType)
vmState.receipts[txIndex] =
vmState.makeReceipt(tx.payload.tx_type.get(TxLegacy))
ok()
proc procBlkPreamble(vmState: BaseVMState;

View File

@ -60,7 +60,7 @@ proc commitOrRollbackDependingOnGasUsed(
# Return remaining gas to the block gas counter so it is
# available for the next transaction.
vmState.gasPool += tx.gasLimit - gasBurned
vmState.gasPool += tx.payload.gas.GasInt - gasBurned
return ok(gasBurned)
proc asyncProcessTransactionImpl(
@ -77,24 +77,25 @@ proc asyncProcessTransactionImpl(
let
roDB = vmState.readOnlyStateDB
baseFee256 = header.eip1559BaseFee(fork)
baseFee = baseFee256.truncate(GasInt)
tx = eip1559TxNormalization(tx, baseFee)
priorityFee = min(tx.maxPriorityFee, tx.maxFee - baseFee)
baseFee = baseFee256
priorityFee = min(
tx.payload.max_priority_fee_per_gas.get(tx.payload.max_fee_per_gas),
tx.payload.max_fee_per_gas - baseFee).truncate(int64)
excessBlobGas = header.excessBlobGas.get(0'u64)
# Return failure unless explicitely set `ok()`
var res: Result[GasInt, string] = err("")
await ifNecessaryGetAccounts(vmState, @[sender, vmState.coinbase()])
if tx.to.isSome:
await ifNecessaryGetCode(vmState, tx.to.get)
if tx.payload.to.isSome:
await ifNecessaryGetCode(vmState, tx.payload.to.unsafeGet)
# buy gas, then the gas goes into gasMeter
if vmState.gasPool < tx.gasLimit:
if vmState.gasPool < tx.payload.gas.GasInt:
return err("gas limit reached. gasLimit=$1, gasNeeded=$2" % [
$vmState.gasPool, $tx.gasLimit])
$vmState.gasPool, $tx.payload.gas])
vmState.gasPool -= tx.gasLimit
vmState.gasPool -= tx.payload.gas.GasInt
# Actually, the eip-1559 reference does not mention an early exit.
#
@ -109,11 +110,11 @@ proc asyncProcessTransactionImpl(
vmState.stateDB.clearTransientStorage()
# Execute the transaction.
vmState.captureTxStart(tx.gasLimit)
vmState.captureTxStart(tx.payload.gas.GasInt)
let
accTx = vmState.stateDB.beginSavepoint
gasBurned = tx.txCallEvm(sender, vmState, fork)
vmState.captureTxEnd(tx.gasLimit - gasBurned)
vmState.captureTxEnd(tx.payload.gas.GasInt - gasBurned)
res = commitOrRollbackDependingOnGasUsed(vmState, accTx, header, tx, gasBurned, priorityFee)
else:

View File

@ -452,7 +452,6 @@ export
tx_item.GasPrice,
tx_item.`<=`,
tx_item.`<`,
tx_item.effectiveGasTip,
tx_item.info,
tx_item.itemID,
tx_item.sender,
@ -630,6 +629,7 @@ proc assembleBlock*(
xp.packerVmExec().isOkOr: # updates vmState
return err(error)
let com = xp.chain.com
var blk = EthBlock(
header: xp.chain.getHeader # uses updated vmState
)
@ -639,20 +639,19 @@ proc assembleBlock*(
for item in nonceList.incNonce:
let tx = item.pooledTx
blk.txs.add tx.tx
if tx.networkPayload != nil:
for k in tx.networkPayload.commitments:
if tx.blob_data.isSome:
if not com.forkGTE(Cancun):
return err("PooledTransaction contains blobs prior to Cancun")
for k in tx.blob_data.unsafeGet.commitments:
blobsBundle.commitments.add k
for p in tx.networkPayload.proofs:
for p in tx.blob_data.unsafeGet.proofs:
blobsBundle.proofs.add p
for blob in tx.networkPayload.blobs:
for blob in tx.blob_data.unsafeGet.blobs:
blobsBundle.blobs.add blob
let com = xp.chain.com
if com.forkGTE(Shanghai):
blk.withdrawals = some(com.pos.withdrawals)
if not com.forkGTE(Cancun) and blobsBundle.commitments.len > 0:
return err("PooledTransaction contains blobs prior to Cancun")
let blobsBundleOpt =
if com.forkGTE(Cancun):
doAssert blobsBundle.commitments.len == blobsBundle.blobs.len

View File

@ -16,7 +16,7 @@ import
../../../common/common,
../../../constants,
../tx_item,
eth/eip1559
eth/[common/transaction, eip1559]
{.push raises: [].}

View File

@ -242,10 +242,10 @@ proc verify*(xp: TxPoolRef): Result[void,TxInfo]
if not initOk or lastSender != item.sender:
initOk = true
lastSender = item.sender
lastNonce = item.tx.nonce
lastNonce = item.tx.payload.nonce
lastSublist = xp.txDB.bySender.eq(item.sender).value.data
elif lastNonce + 1 == item.tx.nonce:
lastNonce = item.tx.nonce
elif lastNonce + 1 == item.tx.payload.nonce:
lastNonce = item.tx.payload.nonce
else:
return err(txInfoVfyNonceChain)
@ -254,12 +254,12 @@ proc verify*(xp: TxPoolRef): Result[void,TxInfo]
of txItemPending:
discard
of txItemStaged:
if lastSublist.eq(txItemPending).eq(item.tx.nonce - 1).isOk:
if lastSublist.eq(txItemPending).eq(item.tx.payload.nonce - 1).isOk:
return err(txInfoVfyNonceChain)
of txItemPacked:
if lastSublist.eq(txItemPending).eq(item.tx.nonce - 1).isOk:
if lastSublist.eq(txItemPending).eq(item.tx.payload.nonce - 1).isOk:
return err(txInfoVfyNonceChain)
if lastSublist.eq(txItemStaged).eq(item.tx.nonce - 1).isOk:
if lastSublist.eq(txItemStaged).eq(item.tx.payload.nonce - 1).isOk:
return err(txInfoVfyNonceChain)
ok()

View File

@ -17,22 +17,14 @@ import
../../utils/ec_recover,
../../utils/utils,
./tx_info,
eth/[common, keys],
eth/[common, common/transaction, keys],
stew/results
export transaction.GasPrice, transaction.GasPriceEx
{.push raises: [].}
type
GasPrice* = ##|
## Handy definition distinct from `GasInt` which is a commodity unit while
## the `GasPrice` is the commodity valuation per unit of gas, similar to a
## kind of currency.
distinct uint64
GasPriceEx* = ##\
## Similar to `GasPrice` but is allowed to be negative.
distinct int64
TxItemStatus* = enum ##\
## Current status of a transaction as seen by the pool.
txItemPending = 0
@ -115,12 +107,9 @@ 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
if rc.isErr:
return err()
ok(T(itemID: itemID,
tx: tx,
sender: rc.value,
sender: tx.tx.signature.from_address,
timeStamp: utcTime(),
info: info,
status: status))
@ -157,22 +146,7 @@ proc itemID*(tx: PooledTransaction): Hash256 =
# core/types/transaction.go(297): func (tx *Transaction) Cost() *big.Int {
proc cost*(tx: Transaction): UInt256 =
## Getter (go/ref compat): gas * gasPrice + value.
(tx.gasPrice * tx.gasLimit).u256 + tx.value
# core/types/transaction.go(332): .. *Transaction) EffectiveGasTip(baseFee ..
# core/types/transaction.go(346): .. EffectiveGasTipValue(baseFee ..
proc effectiveGasTip*(tx: Transaction; baseFee: GasPrice): GasPriceEx =
## The effective miner gas tip for the globally argument `baseFee`. The
## result (which is a price per gas) might well be negative.
if tx.txType < TxEip1559:
(tx.gasPrice - baseFee.int64).GasPriceEx
else:
# London, EIP1559
min(tx.maxPriorityFee, tx.maxFee - baseFee.int64).GasPriceEx
proc effectiveGasTip*(tx: Transaction; baseFee: UInt256): GasPriceEx =
## Variant of `effectiveGasTip()`
tx.effectiveGasTip(baseFee.truncate(uint64).GasPrice)
tx.payload.max_fee_per_gas * tx.payload.gas.u256 + tx.payload.value
# ------------------------------------------------------------------------------
# Public functions, item getters

View File

@ -16,7 +16,7 @@ import
std/[math],
../tx_info,
../tx_item,
eth/[common],
eth/[common, common/transaction],
stew/[results, keyed_queue, keyed_queue/kq_debug, sorted_set]
{.push raises: [].}
@ -120,7 +120,7 @@ proc getRank(schedData: TxSenderSchedRef): int64 =
proc maxProfit(item: TxItemRef; baseFee: GasPrice): float64 =
## Profit calculator
item.tx.gasLimit.float64 * item.tx.effectiveGasTip(baseFee).float64
item.tx.payload.gas.float64 * item.tx.effectiveGasTip(baseFee).float64
proc recalcProfit(nonceData: TxSenderNonceRef; baseFee: GasPrice) =
## Re-calculate profit value depending on `baseFee`
@ -129,7 +129,7 @@ proc recalcProfit(nonceData: TxSenderNonceRef; baseFee: GasPrice) =
while rc.isOk:
let item = rc.value.data
nonceData.profit += item.maxProfit(baseFee)
rc = nonceData.nonceList.gt(item.tx.nonce)
rc = nonceData.nonceList.gt(item.tx.payload.nonce)
# ------------------------------------------------------------------------------
# Private functions
@ -152,7 +152,7 @@ proc mkInxImpl(gt: var TxSenderTab; item: TxItemRef): Result[TxSenderInx,void]
inxData.schedData.allList = inxData.allNonce
else:
inxData.allNonce = inxData.schedData.allList
let rc = inxData.allNonce.nonceList.insert(item.tx.nonce)
let rc = inxData.allNonce.nonceList.insert(item.tx.payload.nonce)
if rc.isErr:
return err()
rc.value.data = item
@ -165,7 +165,7 @@ proc mkInxImpl(gt: var TxSenderTab; item: TxItemRef): Result[TxSenderInx,void]
else:
inxData.statusNonce = inxData.schedData.statusList[item.status]
# this is a new item, checked at `all items sub-list` above
inxData.statusNonce.nonceList.insert(item.tx.nonce).value.data = item
inxData.statusNonce.nonceList.insert(item.tx.payload.nonce).value.data = item
return ok(inxData)
@ -214,10 +214,10 @@ proc insert*(gt: var TxSenderTab; item: TxItemRef): bool
inx.schedData.size.inc
inx.statusNonce.gasLimits += item.tx.gasLimit
inx.statusNonce.gasLimits += item.tx.payload.gas.GasInt
inx.statusNonce.profit += tip
inx.allNonce.gasLimits += item.tx.gasLimit
inx.allNonce.gasLimits += item.tx.payload.gas.GasInt
inx.allNonce.profit += tip
return true
@ -233,21 +233,21 @@ proc delete*(gt: var TxSenderTab; item: TxItemRef): bool
inx.schedData.size.dec
discard inx.allNonce.nonceList.delete(item.tx.nonce)
discard inx.allNonce.nonceList.delete(item.tx.payload.nonce)
if inx.allNonce.nonceList.len == 0:
# this was the last nonce for that sender account
discard gt.addrList.delete(item.sender)
return true
inx.allNonce.gasLimits -= item.tx.gasLimit
inx.allNonce.gasLimits -= item.tx.payload.gas.GasInt
inx.allNonce.profit -= tip
discard inx.statusNonce.nonceList.delete(item.tx.nonce)
discard inx.statusNonce.nonceList.delete(item.tx.payload.nonce)
if inx.statusNonce.nonceList.len == 0:
inx.schedData.statusList[item.status] = nil
return true
inx.statusNonce.gasLimits -= item.tx.gasLimit
inx.statusNonce.gasLimits -= item.tx.payload.gas.GasInt
inx.statusNonce.profit -= tip
return true
@ -293,7 +293,7 @@ proc verify*(gt: var TxSenderTab): Result[void,TxInfo]
let (nonceKey, item) = (rcNonce.value.key, rcNonce.value.data)
rcNonce = statusData.nonceList.gt(nonceKey)
statusGas += item.tx.gasLimit
statusGas += item.tx.payload.gas.GasInt
statusCount.inc
bucketProfit += item.maxProfit(gt.baseFee)
@ -330,7 +330,7 @@ proc verify*(gt: var TxSenderTab): Result[void,TxInfo]
rcNonce = allData.nonceList.gt(nonceKey)
allProfit += item.maxProfit(gt.baseFee)
allGas += item.tx.gasLimit
allGas += item.tx.payload.gas.GasInt
allCount.inc
if differs(allData.profit, allProfit):

View File

@ -77,7 +77,7 @@ proc mkInxImpl(sq: var TxStatusTab; item: TxItemRef): Result[TxStatusInx,void]
inx.addrData.addrList[item.sender] = inx.nonceData
# nonce sublist
let rc = inx.nonceData.nonceList.insert(item.tx.nonce)
let rc = inx.nonceData.nonceList.insert(item.tx.payload.nonce)
if rc.isErr:
return err()
rc.value.data = item
@ -120,7 +120,7 @@ proc insert*(sq: var TxStatusTab; item: TxItemRef): bool
let inx = rc.value
sq.size.inc
inx.addrData.size.inc
inx.addrData.gasLimits += item.tx.gasLimit
inx.addrData.gasLimits += item.tx.payload.gas.GasInt
return true
@ -132,9 +132,9 @@ proc delete*(sq: var TxStatusTab; item: TxItemRef): bool
sq.size.dec
inx.addrData.size.dec
inx.addrData.gasLimits -= item.tx.gasLimit
inx.addrData.gasLimits -= item.tx.payload.gas.GasInt
discard inx.nonceData.nonceList.delete(item.tx.nonce)
discard inx.nonceData.nonceList.delete(item.tx.payload.nonce)
if inx.nonceData.nonceList.len == 0:
discard inx.addrData.addrList.delete(item.sender)
@ -174,7 +174,7 @@ proc verify*(sq: var TxStatusTab): Result[void,TxInfo]
let (nonceKey, item) = (rcNonce.value.key, rcNonce.value.data)
rcNonce = nonceData.nonceList.gt(nonceKey)
gasLimits += item.tx.gasLimit
gasLimits += item.tx.payload.gas.GasInt
addrCount.inc
if addrCount != addrData.size:

View File

@ -72,14 +72,17 @@ proc supersede(xp: TxPoolRef; item: TxItemRef): Result[void,TxInfo]
var current: TxItemRef
block:
let rc = xp.txDB.bySender.eq(item.sender).sub.eq(item.tx.nonce)
let rc = xp.txDB.bySender.eq(item.sender).sub.eq(item.tx.payload.nonce)
if rc.isErr:
return err(txInfoErrUnspecified)
current = rc.value.data
# verify whether replacing is allowed, at all
let bumpPrice = (current.tx.gasPrice * xp.priceBump.GasInt + 99) div 100
if item.tx.gasPrice < current.tx.gasPrice + bumpPrice:
let bumpPrice = (
current.tx.payload.max_fee_per_gas.truncate(int64) *
xp.priceBump.GasInt + 99) div 100
if item.tx.payload.max_fee_per_gas.truncate(int64) <
current.tx.payload.max_fee_per_gas.truncate(int64) + bumpPrice:
discard # return err(txInfoErrReplaceUnderpriced)
# make space, delete item
@ -181,7 +184,7 @@ proc addTxs*(xp: TxPoolRef;
for tx in txs.items:
var reason: TxInfo
if tx.tx.txType == TxEip4844:
if tx.blob_data.isSome:
let res = tx.validateBlobTransactionWrapper()
if res.isErr:
# move item to waste basket
@ -197,7 +200,7 @@ proc addTxs*(xp: TxPoolRef;
else:
let
item = rcTx.value
rcInsert = accTab.getItemList(item.sender).insert(item.tx.nonce)
rcInsert = accTab.getItemList(item.sender).insert(item.tx.payload.nonce)
if rcInsert.isErr:
reason = txInfoErrSenderNonceIndex
else:

View File

@ -53,7 +53,7 @@ proc bucketItemsReassignPending*(xp: TxPoolRef; labelFrom: TxItemStatus;
proc bucketItemsReassignPending*(xp: TxPoolRef; item: TxItemRef)
{.gcsafe,raises: [CatchableError].} =
## Variant of `bucketItemsReassignPending()`
xp.bucketItemsReassignPending(item.status, item.sender, item.tx.nonce)
xp.bucketItemsReassignPending(item.status, item.sender, item.tx.payload.nonce)
proc bucketUpdateAll*(xp: TxPoolRef): bool
@ -71,10 +71,10 @@ proc bucketUpdateAll*(xp: TxPoolRef): bool
for item in xp.pDoubleCheck:
if item.reject == txInfoOk:
# Check whether there was a gap when the head was moved backwards.
let rc = xp.txDB.bySender.eq(item.sender).sub.gt(item.tx.nonce)
let rc = xp.txDB.bySender.eq(item.sender).sub.gt(item.tx.payload.nonce)
if rc.isOk:
let nextItem = rc.value.data
if item.tx.nonce + 1 < nextItem.tx.nonce:
if item.tx.payload.nonce + 1 < nextItem.tx.payload.nonce:
discard xp.disposeItemAndHigherNonces(
item, txInfoErrNonceGap, txInfoErrImpliedNonceGap)
else:

View File

@ -57,19 +57,19 @@ proc checkTxNonce(xp: TxPoolRef; item: TxItemRef): bool
# get the next applicable nonce as registered on the account database
let accountNonce = xp.chain.getNonce(item.sender)
if item.tx.nonce < accountNonce:
if item.tx.payload.nonce < accountNonce:
debug "invalid tx: account nonce too small",
txNonce = item.tx.nonce,
txNonce = item.tx.payload.nonce,
accountNonce
return false
elif accountNonce < item.tx.nonce:
elif accountNonce < item.tx.payload.nonce:
# for an existing account, nonces must come in increasing consecutive order
let rc = xp.txDB.bySender.eq(item.sender)
if rc.isOk:
if rc.value.data.sub.eq(item.tx.nonce - 1).isErr:
if rc.value.data.sub.eq(item.tx.payload.nonce - 1).isErr:
debug "invalid tx: account nonces gap",
txNonce = item.tx.nonce,
txNonce = item.tx.payload.nonce,
accountNonce
return false
@ -87,7 +87,7 @@ proc txNonceActive(xp: TxPoolRef; item: TxItemRef): bool
if rc.isErr:
return true
# Must not be in the `pending` bucket.
if rc.value.data.eq(txItemPending).eq(item.tx.nonce - 1).isOk:
if rc.value.data.eq(txItemPending).eq(item.tx.payload.nonce - 1).isOk:
return false
true
@ -96,30 +96,31 @@ proc txGasCovered(xp: TxPoolRef; item: TxItemRef): bool =
## Check whether the max gas consumption is within the gas limit (aka block
## size).
let trgLimit = xp.chain.limits.trgLimit
if trgLimit < item.tx.gasLimit:
if trgLimit < item.tx.payload.gas.GasInt:
debug "invalid tx: gasLimit exceeded",
maxLimit = trgLimit,
gasLimit = item.tx.gasLimit
gasLimit = item.tx.payload.gas
return false
true
proc txFeesCovered(xp: TxPoolRef; item: TxItemRef): bool =
## Ensure that the user was willing to at least pay the base fee
## And to at least pay the current data gasprice
if item.tx.txType >= TxEip1559:
if item.tx.maxFee.GasPriceEx < xp.chain.baseFee:
if item.tx.payload.tx_type.get(TxLegacy) >= TxEip1559:
if item.tx.payload.max_fee_per_gas.truncate(int64).GasPriceEx <
xp.chain.baseFee:
debug "invalid tx: maxFee is smaller than baseFee",
maxFee = item.tx.maxFee,
maxFee = item.tx.payload.max_fee_per_gas,
baseFee = xp.chain.baseFee
return false
if item.tx.txType >= TxEip4844:
if item.tx.payload.max_fee_per_blob_gas.isSome:
let
excessBlobGas = xp.chain.excessBlobGas
blobGasPrice = getBlobBaseFee(excessBlobGas)
if item.tx.maxFeePerBlobGas < blobGasPrice:
if item.tx.payload.max_fee_per_blob_gas.unsafeGet < blobGasPrice:
debug "invalid tx: maxFeePerBlobGas smaller than blobGasPrice",
maxFeePerBlobGas=item.tx.maxFeePerBlobGas,
maxFeePerBlobGas=item.tx.payload.max_fee_per_blob_gas.unsafeGet,
blobGasPrice=blobGasPrice
return false
true
@ -135,11 +136,11 @@ proc txCostInBudget(xp: TxPoolRef; item: TxItemRef): bool =
require = gasCost
return false
let balanceOffGasCost = balance - gasCost
if balanceOffGasCost < item.tx.value:
if balanceOffGasCost < item.tx.payload.value:
debug "invalid tx: not enough cash to send",
available = balance,
availableMinusGas = balanceOffGasCost,
require = item.tx.value
require = item.tx.payload.value
return false
true
@ -147,10 +148,11 @@ proc txCostInBudget(xp: TxPoolRef; item: TxItemRef): bool =
proc txPreLondonAcceptableGasPrice(xp: TxPoolRef; item: TxItemRef): bool =
## For legacy transactions check whether minimum gas price and tip are
## high enough. These checks are optional.
if item.tx.txType < TxEip1559:
if item.tx.payload.tx_type.get(TxLegacy) < TxEip1559:
if stageItemsPlMinPrice in xp.pFlags:
if item.tx.gasPrice.GasPriceEx < xp.pMinPlGasPrice:
if item.tx.payload.max_fee_per_gas.truncate(int64).GasPriceEx <
xp.pMinPlGasPrice:
return false
elif stageItems1559MinTip in xp.pFlags:
@ -161,14 +163,15 @@ proc txPreLondonAcceptableGasPrice(xp: TxPoolRef; item: TxItemRef): bool =
proc txPostLondonAcceptableTipAndFees(xp: TxPoolRef; item: TxItemRef): bool =
## Helper for `classifyTxPacked()`
if item.tx.txType >= TxEip1559:
if item.tx.payload.tx_type.get(TxLegacy) >= TxEip1559:
if stageItems1559MinTip in xp.pFlags:
if item.tx.effectiveGasTip(xp.chain.baseFee) < xp.pMinTipPrice:
return false
if stageItems1559MinFee in xp.pFlags:
if item.tx.maxFee.GasPriceEx < xp.pMinFeePrice:
if item.tx.payload.max_fee_per_gas.truncate(int64).GasPriceEx <
xp.pMinFeePrice:
return false
true
@ -231,11 +234,10 @@ proc classifyValidatePacked*(xp: TxPoolRef;
xp.chain.limits.maxLimit
else:
xp.chain.limits.trgLimit
tx = item.tx.eip1559TxNormalization(xp.chain.baseFee.GasInt)
excessBlobGas = calcExcessBlobGas(vmState.parent)
roDB.validateTransaction(
tx, item.sender, gasLimit, baseFee, excessBlobGas, fork).isOk
item.tx, item.sender, gasLimit, baseFee, excessBlobGas, fork).isOk
proc classifyPacked*(xp: TxPoolRef; gasBurned, moreBurned: GasInt): bool =
## Classifier for *packing* (i.e. adding up `gasUsed` values after executing

View File

@ -46,7 +46,7 @@ proc deleteOtherNonces(xp: TxPoolRef; item: TxItemRef; newerThan: Time): bool
{.gcsafe,raises: [KeyError].} =
let rc = xp.txDB.bySender.eq(item.sender).sub
if rc.isOk:
for other in rc.value.data.incNonce(item.tx.nonce):
for other in rc.value.data.incNonce(item.tx.payload.nonce):
# only delete non-expired items
if newerThan < other.timeStamp:
discard xp.txDB.dispose(other, txInfoErrTxExpiredImplied)
@ -105,7 +105,7 @@ proc disposeItemAndHigherNonces*(xp: TxPoolRef; item: TxItemRef;
if rc.isOk:
let nonceList = rc.value.data
for otherItem in nonceList.incNonce(item.tx.nonce):
for otherItem in nonceList.incNonce(item.tx.payload.nonce):
if xp.txDB.dispose(otherItem, otherReason):
result.inc

View File

@ -50,7 +50,7 @@ proc insert(xp: TxPoolRef; kq: TxHeadDiffRef; blockHash: Hash256)
{.gcsafe,raises: [CatchableError].} =
let db = xp.chain.com.db
for tx in db.getBlockBody(blockHash).transactions:
if tx.versionedHashes.len > 0:
if tx.payload.blob_versioned_hashes.isSome:
# EIP-4844 blobs are not persisted and cannot be re-broadcasted.
# Note that it is also not possible to crete a cache in all cases,
# as we may have never seen the actual blob sidecar while syncing.

View File

@ -27,7 +27,7 @@ import
../../../transaction,
../../../vm_state,
../../../vm_types,
".."/[tx_chain, tx_desc, tx_item, tx_tabs, tx_tabs/tx_status, tx_info],
".."/[tx_chain, tx_desc, tx_item, tx_tabs, tx_tabs/tx_status],
"."/[tx_bucket, tx_classify]
type
@ -87,11 +87,10 @@ proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt
let
fork = pst.xp.chain.nextFork
baseFee = pst.xp.chain.baseFee
tx = item.tx.eip1559TxNormalization(baseFee.GasInt)
#safeExecutor "tx_packer.runTx":
# # Execute transaction, may return a wildcard `Exception`
result = tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork)
result = item.tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork)
pst.cleanState = false
doAssert 0 <= result
@ -130,14 +129,15 @@ proc runTxCommit(pst: TxPackerStateRef; item: TxItemRef; gasBurned: GasInt)
# Return remaining gas to the block gas counter so it is
# available for the next transaction.
vmState.gasPool += item.tx.gasLimit - gasBurned
vmState.gasPool += item.tx.payload.gas.GasInt - gasBurned
# gasUsed accounting
vmState.cumulativeGasUsed += gasBurned
vmState.receipts[inx] = vmState.makeReceipt(item.tx.txType)
vmState.receipts[inx] =
vmState.makeReceipt(item.tx.payload.tx_type.get(TxLegacy))
# EIP-4844, count blobGasUsed
if item.tx.txType >= TxEip4844:
if item.tx.payload.max_fee_per_blob_gas.isSome:
pst.blobGasUsed += item.tx.getTotalBlobGas
# Update txRoot
@ -175,7 +175,7 @@ proc vmExecInit(xp: TxPoolRef): Result[TxPackerStateRef, string]
let packer = TxPackerStateRef( # return value
xp: xp,
tr: newCoreDbRef(LegacyDbMemory).mptPrune,
tr: newCoreDbRef(LegacyDbMemory, xp.chain.com.chainId).mptPrune,
balance: xp.chain.vmState.readOnlyStateDB.getBalance(xp.chain.feeRecipient),
numBlobPerBlock: 0,
)
@ -189,22 +189,23 @@ proc vmExecGrabItem(pst: TxPackerStateRef; item: TxItemRef): Result[bool,void]
xp = pst.xp
vmState = xp.chain.vmState
if not item.tx.validateChainId(xp.chain.com.chainId):
discard xp.txDB.dispose(item, txInfoChainIdMismatch)
return ok(false) # continue with next account
# EIP-4844
if pst.numBlobPerBlock + item.tx.versionedHashes.len > MAX_BLOBS_PER_BLOCK:
numBlobVersionedHashes =
if item.tx.payload.blob_versioned_hashes.isSome:
item.tx.payload.blob_versioned_hashes.unsafeGet.len
else:
0
if pst.numBlobPerBlock + numBlobVersionedHashes > MAX_BLOBS_PER_BLOCK:
return err() # stop collecting
pst.numBlobPerBlock += item.tx.versionedHashes.len
pst.numBlobPerBlock += numBlobVersionedHashes
# Verify we have enough gas in gasPool
if vmState.gasPool < item.tx.gasLimit:
if vmState.gasPool < item.tx.payload.gas.GasInt:
# skip this transaction and
# continue with next account
# if we don't have enough gas
return ok(false)
vmState.gasPool -= item.tx.gasLimit
vmState.gasPool -= item.tx.payload.gas.GasInt
# Validate transaction relative to the current vmState
if not xp.classifyValidatePacked(vmState, item):

View File

@ -217,12 +217,8 @@ proc validateUncles(com: CommonRef; header: BlockHeader;
# ------------------------------------------------------------------------------
func gasCost*(tx: Transaction): UInt256 =
if tx.txType >= TxEip4844:
tx.gasLimit.u256 * tx.maxFee.u256 + tx.getTotalBlobGas.u256 * tx.maxFeePerBlobGas.u256
elif tx.txType >= TxEip1559:
tx.gasLimit.u256 * tx.maxFee.u256
else:
tx.gasLimit.u256 * tx.gasPrice.u256
tx.payload.gas.u256 * tx.payload.max_fee_per_gas +
tx.getTotalBlobGas.u256 * tx.payload.max_fee_per_blob_gas.get(UInt256.zero)
proc validateTxBasic*(
tx: Transaction; ## tx to validate
@ -230,54 +226,63 @@ proc validateTxBasic*(
validateFork: bool = true): Result[void, string] =
if validateFork:
if tx.txType == TxEip2930 and fork < FkBerlin:
return err("invalid tx: Eip2930 Tx type detected before Berlin")
case tx.payload.tx_type.get(TxLegacy)
of TxEip4844:
if fork < FkCancun:
return err("invalid tx: Eip4844 Tx type detected before Cancun")
of TxEip1559:
if fork < FkLondon:
return err("invalid tx: Eip1559 Tx type detected before London")
of TxEip2930:
if fork < FkBerlin:
return err("invalid tx: Eip2930 Tx type detected before Berlin")
of TxLegacy:
if tx.payload.tx_type.isSome and fork < FkHomestead:
return err("invalid tx: Tx with chain ID detected before Homestead")
if tx.txType == TxEip1559 and fork < FkLondon:
return err("invalid tx: Eip1559 Tx type detected before London")
if tx.txType == TxEip4844 and fork < FkCancun:
return err("invalid tx: Eip4844 Tx type detected before Cancun")
if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
if fork >= FkShanghai and
tx.contractCreation and tx.payload.input.len > EIP3860_MAX_INITCODE_SIZE:
return err("invalid tx: initcode size exceeds maximum")
try:
# The total must be the larger of the two
if tx.maxFee < tx.maxPriorityFee:
return err("invalid tx: maxFee is smaller than maPriorityFee. maxFee=$1, maxPriorityFee=$2" % [
$tx.maxFee, $tx.maxPriorityFee])
if tx.payload.max_fee_per_gas <
tx.payload.max_priority_fee_per_gas.get(tx.payload.max_fee_per_gas):
return err("invalid tx: maxFee is smaller than maxPriorityFee. maxFee=$1, maxPriorityFee=$2" % [
$tx.payload.max_fee_per_gas, $tx.payload.max_priority_fee_per_gas])
if tx.gasLimit < tx.intrinsicGas(fork):
if tx.payload.gas.int64 < tx.intrinsicGas(fork):
return err("invalid tx: not enough gas to perform calculation. avail=$1, require=$2" % [
$tx.gasLimit, $tx.intrinsicGas(fork)])
$tx.payload.gas, $tx.intrinsicGas(fork)])
if fork >= FkCancun:
if tx.payload.len > MAX_CALLDATA_SIZE:
if tx.payload.input.len > MAX_CALLDATA_SIZE:
return err("invalid tx: payload len exceeds MAX_CALLDATA_SIZE. len=" &
$tx.payload.len)
$tx.payload.input.len)
if tx.accessList.len > MAX_ACCESS_LIST_SIZE:
return err("invalid tx: access list len exceeds MAX_ACCESS_LIST_SIZE. len=" &
$tx.accessList.len)
if tx.payload.access_list.isSome:
if tx.payload.access_list.unsafeGet.len > MAX_ACCESS_LIST_SIZE:
return err("invalid tx: access list len exceeds MAX_ACCESS_LIST_SIZE. len=" &
$tx.payload.access_list.unsafeGet.len)
for i, acl in tx.accessList:
if acl.storageKeys.len > MAX_ACCESS_LIST_STORAGE_KEYS:
return err("invalid tx: access list storage keys len exceeds MAX_ACCESS_LIST_STORAGE_KEYS. " &
"index=$1, len=$2" % [$i, $acl.storageKeys.len])
for i, acl in tx.payload.access_list.unsafeGet:
if acl.storage_keys.len > MAX_ACCESS_LIST_STORAGE_KEYS:
return err("invalid tx: access list storage keys len exceeds MAX_ACCESS_LIST_STORAGE_KEYS. " &
"index=$1, len=$2" % [$i, $acl.storage_keys.len])
if tx.txType >= TxEip4844:
if tx.to.isNone:
if tx.payload.tx_type == Opt.some TxEip4844:
if tx.payload.to.isNone:
return err("invalid tx: destination must be not empty")
if tx.versionedHashes.len == 0:
if tx.payload.blob_versioned_hashes.isNone or
tx.payload.blob_versioned_hashes.unsafeGet.len == 0:
return err("invalid tx: there must be at least one blob")
if tx.versionedHashes.len > MAX_BLOBS_PER_BLOCK:
if tx.payload.blob_versioned_hashes.unsafeGet.len > MAX_BLOBS_PER_BLOCK:
return err("invalid tx: versioned hashes len exceeds MAX_BLOBS_PER_BLOCK=" & $MAX_BLOBS_PER_BLOCK &
". get=" & $tx.versionedHashes.len)
". get=" & $tx.payload.blob_versioned_hashes.unsafeGet.len)
for i, bv in tx.versionedHashes:
for i, bv in tx.payload.blob_versioned_hashes.unsafeGet:
if bv.data[0] != VERSIONED_HASH_VERSION_KZG:
return err("invalid tx: one of blobVersionedHash has invalid version. " &
"get=$1, expect=$2" % [$bv.data[0].int, $VERSIONED_HASH_VERSION_KZG.int])
@ -320,14 +325,14 @@ proc validateTransaction*(
# The parallel lowGasLimit.json test never triggers the case checked below
# as the paricular transaction is omitted (the txs list is just set empty.)
try:
if maxLimit < tx.gasLimit:
if maxLimit < tx.payload.gas.int64:
return err("invalid tx: block header gasLimit exceeded. maxLimit=$1, gasLimit=$2" % [
$maxLimit, $tx.gasLimit])
$maxLimit, $tx.payload.gas])
# ensure that the user was willing to at least pay the base fee
if tx.maxFee < baseFee.truncate(int64):
if tx.payload.max_fee_per_gas < baseFee:
return err("invalid tx: maxFee is smaller than baseFee. maxFee=$1, baseFee=$2" % [
$tx.maxFee, $baseFee])
$tx.payload.max_fee_per_gas, $baseFee])
# the signer must be able to fully afford the transaction
let gasCost = tx.gasCost()
@ -336,15 +341,15 @@ proc validateTransaction*(
return err("invalid tx: not enough cash for gas. avail=$1, require=$2" % [
$balance, $gasCost])
if balance - gasCost < tx.value:
if balance - gasCost < tx.payload.value:
return err("invalid tx: not enough cash to send. avail=$1, availMinusGas=$2, require=$3" % [
$balance, $(balance-gasCost), $tx.value])
$balance, $(balance-gasCost), $tx.payload.value])
if tx.nonce != nonce:
if tx.payload.nonce != nonce:
return err("invalid tx: account nonce mismatch. txNonce=$1, accNonce=$2" % [
$tx.nonce, $nonce])
$tx.payload.nonce, $nonce])
if tx.nonce == high(uint64):
if tx.payload.nonce == high(uint64):
return err("invalid tx: nonce at maximum")
# EIP-3607 Reject transactions from senders with deployed code
@ -357,12 +362,13 @@ proc validateTransaction*(
return err("invalid tx: sender is not an EOA. sender=$1, codeHash=$2" % [
sender.toHex, codeHash.data.toHex])
if tx.txType >= TxEip4844:
if tx.payload.max_fee_per_blob_gas.isSome:
# ensure that the user was willing to at least pay the current data gasprice
let blobGasPrice = getBlobBaseFee(excessBlobGas)
if tx.maxFeePerBlobGas < blobGasPrice:
if tx.payload.max_fee_per_blob_gas.unsafeGet < blobGasPrice:
return err("invalid tx: maxFeePerBlobGas smaller than blobGasPrice. " &
"maxFeePerBlobGas=$1, blobGasPrice=$2" % [$tx.maxFeePerBlobGas, $blobGasPrice])
"maxFeePerBlobGas=$1, blobGasPrice=$2" %
[$tx.payload.max_fee_per_blob_gas, $blobGasPrice])
except CatchableError as ex:
return err(ex.msg)

View File

@ -23,9 +23,10 @@ type
# Private helpers
# ------------------------------------------------------------------------------
func toStorageKeys(slots: SlotSet): seq[StorageKey] =
func toStorageKeys(slots: SlotSet): StorageKeys =
for slot in slots:
result.add slot.toBytesBE
let ok = result.add slot.toBytesBE()
doAssert ok, "StorageKeys capacity exceeded"
# ------------------------------------------------------------------------------
# Public constructors
@ -71,10 +72,11 @@ proc clear*(ac: var AccessList) {.inline.} =
func getAccessList*(ac: AccessList): common.AccessList =
for address, slots in ac.slots:
result.add common.AccessPair(
let ok = result.add common.AccessPair(
address : address,
storageKeys: slots.toStorageKeys,
)
doAssert ok, "AccessList capacity exceeded"
func equal*(ac: AccessList, other: var AccessList): bool =
if ac.slots.len != other.slots.len:

View File

@ -148,7 +148,7 @@ type
# Sub-descriptor: KVT methods
# --------------------------------------------------
CoreDbKvtBackendFn* = proc(): CoreDbKvtBackendRef {.noRaise.}
CoreDbKvtGetFn* = proc(k: openArray[byte]): CoreDbRc[Blob] {.noRaise.}
CoreDbKvtGetFn* = proc(k: openArray[byte]): CoreDbRc[Blob] {.noRaise.}
CoreDbKvtDelFn* = proc(k: openArray[byte]): CoreDbRc[void] {.noRaise.}
CoreDbKvtPutFn* =
proc(k: openArray[byte]; v: openArray[byte]): CoreDbRc[void] {.noRaise.}
@ -287,6 +287,7 @@ type
profTab*: CoreDbProfListRef ## Profiling data (if any)
ledgerHook*: RootRef ## Debugging/profiling, to be used by ledger
methods*: CoreDbBaseFns
chainId*: ChainId
CoreDbErrorRef* = ref object of RootRef
## Generic error object

View File

@ -16,7 +16,7 @@
import
std/[algorithm, options, sequtils],
chronicles,
eth/[common, rlp],
eth/[common, common/transaction, rlp],
results,
stew/byteutils,
"../.."/[errors, constants],
@ -150,7 +150,8 @@ iterator getBlockTransactions*(
): Transaction
{.gcsafe, raises: [RlpError].} =
for encodedTx in db.getBlockTransactionData(header.txRoot):
yield rlp.decode(encodedTx, Transaction)
yield Transaction.fromBytes(encodedTx, db.chainId).valueOr:
raise (ref MalformedRlpError)(msg: "Invalid transaction in block")
iterator getBlockTransactionHashes*(
@ -161,8 +162,9 @@ iterator getBlockTransactionHashes*(
## Returns an iterable of the transaction hashes from th block specified
## by the given block header.
for encodedTx in db.getBlockTransactionData(blockHeader.txRoot):
let tx = rlp.decode(encodedTx, Transaction)
yield rlpHash(tx) # beware EIP-4844
let tx = Transaction.fromBytes(encodedTx, db.chainId).valueOr:
raise (ref MalformedRlpError)(msg: "Invalid transaction in block")
yield tx.compute_tx_hash(db.chainId)
iterator getWithdrawalsData*(

View File

@ -53,6 +53,7 @@ export
proc newCoreDbRef*(
db: TrieDatabaseRef;
chainId: ChainId;
): CoreDbRef
{.gcsafe, deprecated: "use newCoreDbRef(LegacyDbPersistent,<path>)".} =
## Legacy constructor.
@ -60,10 +61,13 @@ proc newCoreDbRef*(
## Note: Using legacy notation `newCoreDbRef()` rather than
## `CoreDbRef.init()` because of compiler coughing.
##
db.newLegacyPersistentCoreDbRef()
let res = db.newLegacyPersistentCoreDbRef()
res.chainId = chainId
res
proc newCoreDbRef*(
dbType: static[CoreDbType]; # Database type symbol
chainId: ChainId;
): CoreDbRef =
## Constructor for volatile/memory type DB
##
@ -71,19 +75,23 @@ proc newCoreDbRef*(
## `CoreDbRef.init()` because of compiler coughing.
##
when dbType == LegacyDbMemory:
newLegacyMemoryCoreDbRef()
let res = newLegacyMemoryCoreDbRef()
elif dbType == AristoDbMemory:
newAristoMemoryCoreDbRef()
let res = newAristoMemoryCoreDbRef()
elif dbType == AristoDbVoid:
newAristoVoidCoreDbRef()
let res = newAristoVoidCoreDbRef()
else:
{.error: "Unsupported constructor " & $dbType & ".newCoreDbRef()".}
res.chainId = chainId
res
proc newCoreDbRef*(
dbType: static[CoreDbType]; # Database type symbol
chainId: ChainId;
qidLayout: QidLayoutRef; # `Aristo` only
): CoreDbRef =
## Constructor for volatile/memory type DB
@ -92,15 +100,18 @@ proc newCoreDbRef*(
## `CoreDbRef.init()` because of compiler coughing.
##
when dbType == AristoDbMemory:
newAristoMemoryCoreDbRef(DefaultQidLayoutRef)
let res = newAristoMemoryCoreDbRef(DefaultQidLayoutRef)
elif dbType == AristoDbVoid:
newAristoVoidCoreDbRef()
let res = newAristoVoidCoreDbRef()
else:
{.error: "Unsupported constructor " & $dbType & ".newCoreDbRef()" &
" with qidLayout argument".}
res.chainId = chainId
res
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -35,23 +35,28 @@ export
proc newCoreDbRef*(
dbType: static[CoreDbType]; # Database type symbol
path: string; # Storage path for database
chainId: ChainId;
): CoreDbRef =
## Constructor for persistent type DB
##
## Note: Using legacy notation `newCoreDbRef()` rather than
## `CoreDbRef.init()` because of compiler coughing.
when dbType == LegacyDbPersistent:
newLegacyPersistentCoreDbRef path
let res = newLegacyPersistentCoreDbRef path
elif dbType == AristoDbRocks:
newAristoRocksDbCoreDbRef path
let res = newAristoRocksDbCoreDbRef path
else:
{.error: "Unsupported dbType for persistent newCoreDbRef()".}
res.chainId = chainId
res
proc newCoreDbRef*(
dbType: static[CoreDbType]; # Database type symbol
path: string; # Storage path for database
chainId: ChainId;
qidLayout: QidLayoutRef; # Optional for `Aristo`, ignored by others
): CoreDbRef =
## Constructor for persistent type DB
@ -59,10 +64,13 @@ proc newCoreDbRef*(
## Note: Using legacy notation `newCoreDbRef()` rather than
## `CoreDbRef.init()` because of compiler coughing.
when dbType == AristoDbRocks:
newAristoRocksDbCoreDbRef(path, qlr)
let res = newAristoRocksDbCoreDbRef(path, qlr)
else:
{.error: "Unsupported dbType for persistent newCoreDbRef()" &
" with qidLayout argument".}
res.chainId = chainId
res
# End

View File

@ -49,6 +49,7 @@ type
db: ReadOnlyStateDB
TxNode = ref object of Node
chainId: ChainId
tx: Transaction
index: int
blockNumber: common.BlockNumber
@ -113,6 +114,7 @@ proc txNode(ctx: GraphqlContextRef, tx: Transaction, index: int, blockNumber: co
kind: nkMap,
typeName: ctx.ids[ethTransaction],
pos: Pos(),
chainId: ctx.com.chainId,
tx: tx,
index: index,
blockNumber: blockNumber,
@ -282,7 +284,8 @@ proc getTxs(ctx: GraphqlContextRef, header: common.BlockHeader): RespResult =
var list = respList()
var index = 0
for n in getBlockTransactionData(ctx.chainDB, header.txRoot):
let tx = decodeTx(n)
let tx = Transaction.fromBytes(n, ctx.com.chainId).valueOr:
raise (ref MalformedRlpError)(msg: "Invalid transaction in block")
list.add txNode(ctx, tx, index, header.blockNumber, header.fee)
inc index
@ -614,7 +617,7 @@ proc txHash(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
proc txNonce(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
longNode(tx.tx.nonce)
longNode(tx.tx.payload.nonce)
proc txIndex(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
@ -653,61 +656,65 @@ proc txTo(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
if hres.isErr:
return hres
let h = HeaderNode(hres.get())
ctx.accountNode(h.header, tx.tx.to.get())
ctx.accountNode(h.header, tx.tx.payload.to.get())
proc txValue(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
bigIntNode(tx.tx.value)
bigIntNode(tx.tx.payload.value)
proc txGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
if tx.tx.txType == TxEip1559:
if tx.tx.payload.max_priority_fee_per_gas.isSome:
if tx.baseFee.isNone:
return bigIntNode(tx.tx.gasPrice)
return bigIntNode(tx.tx.payload.max_fee_per_gas)
let baseFee = tx.baseFee.get().truncate(GasInt)
let priorityFee = min(tx.tx.maxPriorityFee, tx.tx.maxFee - baseFee)
let priorityFee = min(
tx.tx.payload.max_priority_fee_per_gas.unsafeGet.truncate(int64),
tx.tx.payload.max_fee_per_gas.truncate(int64) - baseFee)
bigIntNode(priorityFee + baseFee)
else:
bigIntNode(tx.tx.gasPrice)
bigIntNode(tx.tx.payload.max_fee_per_gas)
proc txMaxFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
if tx.tx.txType == TxEip1559:
bigIntNode(tx.tx.maxFee)
if tx.tx.payload.max_priority_fee_per_gas.isSome:
bigIntNode(tx.tx.payload.max_fee_per_gas)
else:
ok(respNull())
proc txMaxPriorityFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
if tx.tx.txType == TxEip1559:
bigIntNode(tx.tx.maxPriorityFee)
if tx.tx.payload.max_priority_fee_per_gas.isSome:
bigIntNode(tx.tx.payload.max_priority_fee_per_gas.unsafeGet)
else:
ok(respNull())
proc txEffectiveGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
if tx.baseFee.isNone:
return bigIntNode(tx.tx.gasPrice)
return bigIntNode(tx.tx.payload.max_fee_per_gas.truncate(int64))
let baseFee = tx.baseFee.get().truncate(GasInt)
let priorityFee = min(tx.tx.maxPriorityFee, tx.tx.maxFee - baseFee)
let priorityFee = min(
tx.tx.payload.max_priority_fee_per_gas.unsafeGet.truncate(int64),
tx.tx.payload.max_fee_per_gas.truncate(int64) - baseFee)
bigIntNode(priorityFee + baseFee)
proc txChainId(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
if tx.tx.txType == TxLegacy:
if tx.tx.payload.tx_type.isNone:
ok(respNull())
else:
longNode(tx.tx.chainId.uint64)
longNode(tx.chainId.uint64)
proc txGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
longNode(tx.tx.gasLimit)
longNode(tx.tx.payload.gas)
proc txInputData(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
resp(tx.tx.payload)
resp(distinctBase(tx.tx.payload.input))
proc txBlock(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let ctx = GraphqlContextRef(ud)
@ -743,7 +750,7 @@ proc txCreatedContract(ud: RootRef, params: Args, parent: Node): RespResult {.ap
if hres.isErr:
return hres
let h = HeaderNode(hres.get())
let contractAddress = generateAddress(sender, tx.tx.nonce)
let contractAddress = generateAddress(sender, tx.tx.payload.nonce)
ctx.accountNode(h.header, contractAddress)
proc txLogs(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
@ -756,46 +763,55 @@ proc txLogs(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
proc txR(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
bigIntNode(tx.tx.R)
bigIntNode(ecdsa_unpack_signature(tx.tx.signature.ecdsa_signature).r)
proc txS(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
bigIntNode(tx.tx.S)
bigIntNode(ecdsa_unpack_signature(tx.tx.signature.ecdsa_signature).s)
proc txV(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
bigIntNode(tx.tx.V)
let
tx = TxNode(parent)
yParity = ecdsa_unpack_signature(tx.tx.signature.ecdsa_signature).y_parity
v =
if tx.tx.payload.tx_type.isNone:
if yParity: 28.u256 else: 27.u256
elif tx.tx.payload.tx_type == Opt.some TxLegacy:
distinctBase(tx.chainId).u256 * 2 + (if yParity: 36.u256 else: 35.u256)
else:
if yParity: UInt256.one else: UInt256.zero
bigIntNode(v)
proc txType(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
let typ = resp(ord(tx.tx.txType))
let typ = resp(ord(tx.tx.payload.tx_type.get(TxLegacy)))
ok(typ)
proc txAccessList(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let ctx = GraphqlContextRef(ud)
let tx = TxNode(parent)
if tx.tx.txType == TxLegacy:
if tx.tx.payload.access_list.isNone:
ok(respNull())
else:
var list = respList()
for x in tx.tx.accessList:
for x in tx.tx.payload.access_list.unsafeGet:
list.add aclNode(ctx, x)
ok(list)
proc txMaxFeePerBlobGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
if tx.tx.txType < TxEIP4844:
if tx.tx.payload.max_fee_per_blob_gas.isNone:
ok(respNull())
else:
longNode(tx.tx.maxFeePerBlobGas)
longNode(tx.tx.payload.max_fee_per_blob_gas.unsafeGet)
proc txVersionedHashes(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} =
let tx = TxNode(parent)
if tx.tx.txType < TxEIP4844:
if tx.tx.payload.blob_versioned_hashes.isNone:
ok(respNull())
else:
var list = respList()
for hs in tx.tx.versionedHashes:
for hs in tx.tx.payload.blob_versioned_hashes.unsafeGet:
list.add resp("0x" & hs.data.toHex)
ok(list)
@ -1364,8 +1380,10 @@ proc sendRawTransaction(ud: RootRef, params: Args, parent: Node): RespResult {.a
let ctx = GraphqlContextRef(ud)
try:
let data = hexToSeqByte(params[0].val.stringVal)
let tx = decodePooledTx(data) # we want to know if it is a valid tx blob
let txHash = rlpHash(tx)
# we want to know if it is a valid tx blob
let tx = PooledTransaction.fromBytes(data, ctx.com.chainId).valueOr:
return err("Invalid `PooledTransaction`")
let txHash = tx.tx.compute_tx_hash(ctx.com.chainId)
ctx.txPool.add(tx)

View File

@ -283,8 +283,12 @@ proc start(nimbus: NimbusNode, conf: NimbusConf) =
let coreDB =
# Resolve statically for database type
case conf.chainDbMode:
of Prune,Archive: LegacyDbPersistent.newCoreDbRef(string conf.dataDir)
of Aristo: AristoDbRocks.newCoreDbRef(string conf.dataDir)
of Prune,Archive:
LegacyDbPersistent.newCoreDbRef(
string conf.dataDir, conf.networkParams.config.chainId)
of Aristo:
AristoDbRocks.newCoreDbRef(
string conf.dataDir, conf.networkParams.config.chainId)
let com = CommonRef.new(
db = coreDB,
pruneTrie = (conf.chainDbMode == ChainDbMode.Prune),

View File

@ -8,7 +8,7 @@
# those terms.
import
std/[hashes, algorithm, strutils],
std/[hashes, algorithm, strutils, typetraits],
eth/eip1559,
stew/keyed_queue,
stew/endians2,
@ -125,11 +125,11 @@ proc processBlock(oracle: Oracle, bc: BlockContent, percentiles: openArray[float
for i, tx in bc.txs:
let
reward = tx.effectiveGasTip(bc.header.fee)
reward = tx.effectiveGasTip(bc.header.fee.get(UInt256.zero))
gasUsed = bc.receipts[i].cumulativeGasUsed - prevUsed
sorter[i] = TxGasAndReward(
gasUsed: gasUsed.uint64,
reward: reward.u256
reward: distinctBase(reward).u256
)
prevUsed = bc.receipts[i].cumulativeGasUsed

View File

@ -259,9 +259,9 @@ proc setupEthRpc*(
let
accDB = stateDBFromTag(blockId("latest"))
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1)
eip155 = com.isEIP155(com.syncCurrent)
signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155)
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1, eip155)
signedTx = signTransaction(tx, acc.privateKey, com.chainId)
result = rlp.encode(signedTx)
server.rpc("eth_sendTransaction") do(data: TransactionArgs) -> Web3Hash:
@ -279,31 +279,40 @@ proc setupEthRpc*(
let
accDB = stateDBFromTag(blockId("latest"))
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1)
eip155 = com.isEIP155(com.syncCurrent)
signedTx = signTransaction(tx, acc.privateKey, com.chainId, eip155)
tx = unsignedTx(data, chainDB, accDB.getNonce(address) + 1, eip155)
signedTx = signTransaction(tx, acc.privateKey, com.chainId)
networkPayload =
if signedTx.txType == TxEip4844:
if signedTx.payload.blob_versioned_hashes.isSome:
template vhs: untyped =
signedTx.payload.blob_versioned_hashes.unsafeGet
if data.blobs.isNone or data.commitments.isNone or data.proofs.isNone:
raise newException(ValueError, "EIP-4844 transaction needs blobs")
if data.blobs.get.len != signedTx.versionedHashes.len:
if data.blobs.get.len != vhs.len:
raise newException(ValueError, "Incorrect number of blobs")
if data.commitments.get.len != signedTx.versionedHashes.len:
if data.commitments.get.len != vhs.len:
raise newException(ValueError, "Incorrect number of commitments")
if data.proofs.get.len != signedTx.versionedHashes.len:
if data.proofs.get.len != vhs.len:
raise newException(ValueError, "Incorrect number of proofs")
NetworkPayload(
blobs: data.blobs.get.mapIt it.NetworkBlob,
commitments: data.commitments.get.mapIt eth_types.KzgCommitment(it),
proofs: data.proofs.get.mapIt eth_types.KzgProof(it))
Opt.some NetworkPayload(
blobs:
List[NetworkBlob, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(data.blobs.get.mapIt it.NetworkBlob),
commitments:
List[eth_types.KzgCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(data.commitments.get.mapIt eth_types.KzgCommitment(it)),
proofs:
List[eth_types.KzgProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
.init(data.proofs.get.mapIt eth_types.KzgProof(it)))
else:
if data.blobs.isSome or data.commitments.isSome or data.proofs.isSome:
raise newException(ValueError, "Blobs require EIP-4844 transaction")
nil
pooledTx = PooledTransaction(tx: signedTx, networkPayload: networkPayload)
Opt.none NetworkPayload
pooledTx = PooledTransaction(tx: signedTx, blob_data: networkPayload)
txPool.add(pooledTx)
result = rlpHash(signedTx).w3Hash
result = signedTx.compute_tx_hash(com.chainId).w3Hash
server.rpc("eth_sendRawTransaction") do(txBytes: seq[byte]) -> Web3Hash:
## Creates new message call transaction or a contract creation for signed transactions.
@ -312,8 +321,9 @@ proc setupEthRpc*(
## Returns the transaction hash, or the zero hash if the transaction is not yet available.
## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract.
let
pooledTx = decodePooledTx(txBytes)
txHash = rlpHash(pooledTx)
pooledTx = PooledTransaction.fromBytes(txBytes, com.chainId).valueOr:
raise (ref MalformedRlpError)(msg: "Invalid `PooledTransaction`")
txHash = pooledTx.tx.compute_tx_hash(com.chainId)
txPool.add(pooledTx)
let res = txPool.inPoolAndReason(txHash)
@ -381,7 +391,7 @@ proc setupEthRpc*(
let txHash = data.ethHash()
let res = txPool.getItem(txHash)
if res.isOk:
return populateTransactionObject(res.get().tx)
return populateTransactionObject(res.get().tx, com.chainId)
let txDetails = chainDB.getTransactionKey(txHash)
if txDetails.index < 0:
@ -390,7 +400,8 @@ proc setupEthRpc*(
let header = chainDB.getBlockHeader(txDetails.blockNumber)
var tx: Transaction
if chainDB.getTransaction(header.txRoot, txDetails.index, tx):
result = populateTransactionObject(tx, some(header), some(txDetails.index))
result = populateTransactionObject(
tx, com.chainId, some(header), some(txDetails.index))
server.rpc("eth_getTransactionByBlockHashAndIndex") do(data: Web3Hash, quantity: Web3Quantity) -> TransactionObject:
## Returns information about a transaction by block hash and transaction index position.
@ -405,7 +416,8 @@ proc setupEthRpc*(
var tx: Transaction
if chainDB.getTransaction(header.txRoot, index, tx):
result = populateTransactionObject(tx, some(header), some(index))
result = populateTransactionObject(
tx, com.chainId, some(header), some(index))
else:
result = nil
@ -420,7 +432,8 @@ proc setupEthRpc*(
var tx: Transaction
if chainDB.getTransaction(header.txRoot, index, tx):
result = populateTransactionObject(tx, some(header), some(index))
result = populateTransactionObject(
tx, com.chainId, some(header), some(index))
else:
result = nil

View File

@ -62,9 +62,8 @@ proc calculateMedianGasPrice*(chain: CoreDbRef): GasInt
{.gcsafe, raises: [CatchableError].} =
var prices = newSeqOfCap[GasInt](64)
let header = chain.getCanonicalHead()
for encodedTx in chain.getBlockTransactionData(header.txRoot):
let tx = decodeTx(encodedTx)
prices.add(tx.gasPrice)
for tx in chain.getBlockTransactions(header):
prices.add(tx.payload.max_fee_per_gas.truncate(int64))
if prices.len > 0:
sort(prices)
@ -79,32 +78,48 @@ 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
{.gcsafe, raises: [CatchableError].} =
if tx.to.isSome:
result.to = some(ethAddr(tx.to.get))
if tx.gas.isSome:
result.gasLimit = tx.gas.get.GasInt
else:
result.gasLimit = 90000.GasInt
if tx.gasPrice.isSome:
result.gasPrice = tx.gasPrice.get.GasInt
else:
result.gasPrice = calculateMedianGasPrice(chain)
if tx.value.isSome:
result.value = tx.value.get
else:
result.value = 0.u256
if tx.nonce.isSome:
result.nonce = tx.nonce.get.AccountNonce
else:
result.nonce = defaultNonce
result.payload = tx.payload
proc unsignedTx*(
tx: TransactionArgs,
chain: CoreDbRef,
defaultNonce: AccountNonce,
eip155 = true
): TransactionPayload {.gcsafe, raises: [CatchableError].} =
TransactionPayload(
nonce:
if tx.nonce.isSome:
tx.nonce.get.AccountNonce
else:
defaultNonce,
max_fee_per_gas:
if tx.gasPrice.isSome:
distinctBase(tx.gasPrice.get).u256
else:
calculateMedianGasPrice(chain).uint64.u256,
gas:
if tx.gas.isSome:
distinctBase(tx.gas.get)
else:
90000,
to:
if tx.to.isSome:
Opt.some ethAddr(tx.to.get)
else:
Opt.none(EthAddress),
value:
if tx.value.isSome:
tx.value.get
else:
UInt256.zero,
input:
if tx.payload.len > MAX_CALL_DATA_SIZE:
raise (ref ValueError)(msg: "tx.payload exceeds MAX_CALL_DATA_SIZE")
else:
List[byte, Limit MAX_CALL_DATA_SIZE].init(tx.payload),
tx_type:
if eip155:
Opt.some TxLegacy
else:
Opt.none TxType)
proc toWd(wd: Withdrawal): WithdrawalObject =
WithdrawalObject(
@ -120,39 +135,61 @@ proc toWdList(list: openArray[Withdrawal]): seq[WithdrawalObject] =
result.add toWd(x)
proc populateTransactionObject*(tx: Transaction,
chainId: ChainId,
optionalHeader: Option[BlockHeader] = none(BlockHeader),
txIndex: Option[int] = none(int)): TransactionObject
{.gcsafe, raises: [ValidationError].} =
result = TransactionObject()
result.`type` = some w3Qty(tx.txType.ord)
if optionalHeader.isSome:
let header = optionalHeader.get
result.blockHash = some(w3Hash header.hash)
result.blockNumber = some(w3BlockNumber(header.blockNumber))
let anyTx = AnyTransaction.fromOneOfBase(tx).valueOr:
raiseAssert "Cannot convert invalid `Transaction`: " & $tx
withTxVariant(anyTx):
result = TransactionObject()
when txKind >= TransactionKind.Legacy:
result.`type` = options.some w3Qty(txVariant.payload.tx_type.ord)
if optionalHeader.isSome:
let header = optionalHeader.get
result.blockHash = some(w3Hash header.hash)
result.blockNumber = some(w3BlockNumber(header.blockNumber))
result.`from` = w3Addr tx.getSender()
result.gas = w3Qty(tx.gasLimit)
result.gasPrice = w3Qty(tx.gasPrice)
result.hash = w3Hash tx.rlpHash
result.input = tx.payload
result.nonce = w3Qty(tx.nonce)
result.to = some(w3Addr tx.destination)
if txIndex.isSome:
result.transactionIndex = some(w3Qty(txIndex.get))
result.value = tx.value
result.v = w3Qty(tx.V)
result.r = u256(tx.R)
result.s = u256(tx.S)
result.maxFeePerGas = some w3Qty(tx.maxFee)
result.maxPriorityFeePerGas = some w3Qty(tx.maxPriorityFee)
result.`from` = w3Addr txVariant.signature.from_address
result.gas = w3Qty(txVariant.payload.gas)
result.gasPrice = w3Qty(txVariant.payload.max_fee_per_gas)
result.hash = w3Hash txVariant.payload.compute_sig_hash(chainId)
result.input = distinctBase(txVariant.payload.input)
result.nonce = w3Qty(txVariant.payload.nonce)
when txKind == TransactionKind.Eip4844:
result.to = some(w3Addr txVariant.payload.to)
else:
if txVariant.payload.to.isSome:
result.to = some(w3Addr txVariant.payload.to.unsafeGet)
if txIndex.isSome:
result.transactionIndex = some(w3Qty(txIndex.get))
result.value = txVariant.payload.value
let
(yParity, r, s) = ecdsa_unpack_signature(
txVariant.signature.ecdsa_signature)
v =
when txKind == TransactionKind.Replayable:
if yParity: 28.u256 else: 27.u256
elif txKind == TransactionKind.Legacy:
distinctBase(chainId).u256 * 2 + (if yParity: 36.u256 else: 35.u256)
else:
if yParity: UInt256.one else: UInt256.zero
result.v = w3Qty(v)
result.r = u256(r)
result.s = u256(s)
when txKind >= TransactionKind.Eip1559:
result.maxFeePerGas = some w3Qty(txVariant.payload.max_fee_per_gas)
result.maxPriorityFeePerGas = some w3Qty(
txVariant.payload.max_priority_fee_per_gas)
if tx.txType >= TxEip2930:
result.chainId = some(Web3Quantity(tx.chainId))
result.accessList = some(w3AccessList(tx.accessList))
when txKind >= TransactionKind.Eip2930:
result.chainId = some(Web3Quantity(chainId))
result.accessList = some(w3AccessList(txVariant.payload.access_list))
if tx.txType >= TxEIP4844:
result.maxFeePerBlobGas = some(tx.maxFeePerBlobGas)
result.blobVersionedHashes = some(w3Hashes tx.versionedHashes)
when txKind == TransactionKind.Eip4844:
result.maxFeePerBlobGas = some(txVariant.payload.max_fee_per_blob_gas)
result.blobVersionedHashes =
some(w3Hashes distinctBase(txVariant.payload.blob_versioned_hashes))
proc populateBlockObject*(header: BlockHeader, chain: CoreDbRef, fullTx: bool, isUncle = false): BlockObject
{.gcsafe, raises: [CatchableError].} =
@ -191,7 +228,8 @@ proc populateBlockObject*(header: BlockHeader, chain: CoreDbRef, fullTx: bool, i
if fullTx:
var i = 0
for tx in chain.getBlockTransactions(header):
result.transactions.add txOrHash(populateTransactionObject(tx, some(header), some(i)))
result.transactions.add txOrHash(
populateTransactionObject(tx, chain.chainId, some(header), some(i)))
inc i
else:
for x in chain.getBlockTransactionHashes(header):
@ -227,7 +265,7 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction,
if tx.contractCreation:
var sender: EthAddress
if tx.getSender(sender):
let contractAddress = generateAddress(sender, tx.nonce)
let contractAddress = generateAddress(sender, tx.payload.nonce)
result.contractAddress = some(w3Addr contractAddress)
for log in receipt.logs:
@ -263,11 +301,15 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction,
# 1 = success, 0 = failure.
result.status = some(w3Qty(receipt.status.uint64))
let normTx = eip1559TxNormalization(tx, header.baseFee.truncate(GasInt))
result.effectiveGasPrice = w3Qty(normTx.gasPrice)
result.effectiveGasPrice = w3Qty(
(header.baseFee + min(
tx.payload.max_priority_fee_per_gas.get(tx.payload.max_fee_per_gas),
tx.payload.max_fee_per_gas - header.baseFee)).truncate(int64))
if tx.txType == TxEip4844:
result.blobGasUsed = some(w3Qty(tx.versionedHashes.len.uint64 * GAS_PER_BLOB.uint64))
if tx.payload.blob_versioned_hashes.isSome:
result.blobGasUsed = some(w3Qty(
tx.payload.blob_versioned_hashes.unsafeGet.len.uint64 *
GAS_PER_BLOB.uint64))
result.blobGasPrice = some(getBlobBaseFee(header.excessBlobGas.get(0'u64)))
proc createAccessList*(header: BlockHeader,

View File

@ -11,9 +11,10 @@
{.push raises: [].}
import
std/[tables, times, hashes, sets],
std/[tables, times, hashes, sets, typetraits],
chronicles, chronos,
stew/endians2,
eth/common/transaction,
eth/p2p,
eth/p2p/peer_pool,
".."/[types, protocol],
@ -258,7 +259,7 @@ proc sendTransactions(ctx: EthWireRef,
# This is used to avoid re-sending along pooledTxHashes
# announcements/re-broadcasts
ctx.addToKnownByPeer(txHashes, peer)
await peer.transactions(txs)
await peer.transactions(RawRlp txs.toBytes(ctx.chainId))
except TransportError:
debug "Transport got closed during sendTransactions"
@ -276,16 +277,19 @@ proc fetchTransactions(ctx: EthWireRef, reqHashes: seq[Hash256], peer: Peer): Fu
error "not able to get pooled transactions"
return
let txs = res.get()
let txs = seq[PooledTransaction].fromBytes(
distinctBase(res.get().transactions), ctx.chainId).valueOr:
error "invalid pooled transactions"
return
debug "fetchTx: received requested txs",
number = txs.transactions.len
number = txs.len
# Remove from pending list regardless if tx is in result
for tx in txs.transactions:
let txHash = rlpHash(tx)
for tx in txs:
let txHash = tx.tx.compute_tx_hash(ctx.chainId)
ctx.pending.excl txHash
ctx.txPool.add(txs.transactions)
ctx.txPool.add(txs)
except TransportError:
debug "Transport got closed during fetchTransactions"
@ -353,6 +357,7 @@ proc new*(_: type EthWireRef,
txPool: TxPoolRef,
peerPool: PeerPool): EthWireRef =
let ctx = EthWireRef(
chainId: chain.com.chainId,
db: chain.db,
chain: chain,
txPool: txPool,
@ -523,7 +528,7 @@ method handleAnnouncedTxs*(ctx: EthWireRef,
ctx.addToKnownByPeer(txHashes, peer)
for tx in txs:
if tx.versionedHashes.len > 0:
if tx.payload.blob_versioned_hashes.isSome:
# EIP-4844 blobs are not persisted and cannot be broadcasted
continue
ctx.txPool.add PooledTransaction(tx: tx)
@ -533,7 +538,8 @@ method handleAnnouncedTxs*(ctx: EthWireRef,
for i, txHash in txHashes:
# Nodes must not automatically broadcast blob transactions to
# their peers. per EIP-4844 spec
if ctx.txPool.inPoolAndOk(txHash) and txs[i].txType != TxEip4844:
if ctx.txPool.inPoolAndOk(txHash) and
txs[i].payload.blob_versioned_hashes.isNone:
newTxHashes.add txHash
validTxs.add txs[i]

View File

@ -32,6 +32,7 @@ type
forkNext*: uint64 # The RLP encoding must be variable-length
EthWireBase* = ref object of RootRef
chainId*: ChainId
EthState* = object
totalDifficulty*: DifficultyInt

View File

@ -263,11 +263,11 @@ p2pProtocol eth66(version = ethVersion,
trace trEthSendReplying & "EMPTY PooledTransactions (0x0a)", peer,
sent=0, requested=txHashes.len
await response.send(txs.get)
await response.send(RawRlp(txs.get.toBytes(ctx.chainId)))
# User message 0x0a: PooledTransactions.
proc pooledTransactions(
peer: Peer, transactions: openArray[PooledTransaction])
peer: Peer, transactions: RawRlp)
nextId 0x0d

View File

@ -14,10 +14,11 @@
## `eth/67 <https://github.com/ethereum/devp2p/blob/master/caps/eth.md>`_
import
std/typetraits,
stint,
chronicles,
chronos,
eth/[common, p2p, p2p/private/p2p_types],
eth/[common, common/transaction, p2p, p2p/private/p2p_types],
stew/byteutils,
./trace_config,
./eth/eth_types,
@ -161,12 +162,17 @@ p2pProtocol eth67(version = ethVersion,
handleHandlerError(res)
# User message 0x02: Transactions.
proc transactions(peer: Peer, transactions: openArray[Transaction]) =
proc transactions(peer: Peer, encodedTransactions: RawRlp) =
let
ctx = peer.networkState()
transactions = seq[Transaction].fromBytes(
distinctBase(encodedTransactions), ctx.chainId).valueOr:
raise (ref MalformedRlpError)(msg: "Invalid transaction in message")
when trEthTraceGossipOk:
trace trEthRecvReceived & "Transactions (0x02)", peer,
transactions=transactions.len
let ctx = peer.networkState()
let res = ctx.handleAnnouncedTxs(peer, transactions)
handleHandlerError(res)
@ -264,11 +270,11 @@ p2pProtocol eth67(version = ethVersion,
trace trEthSendReplying & "EMPTY PooledTransactions (0x0a)", peer,
sent=0, requested=txHashes.len
await response.send(txs.get)
await response.send(RawRlp(txs.get.toBytes(ctx.chainId)))
# User message 0x0a: PooledTransactions.
proc pooledTransactions(
peer: Peer, transactions: openArray[PooledTransaction])
peer: Peer, transactions: RawRlp)
# User message 0x0d: GetNodeData -- removed, was so 66ish
# User message 0x0e: NodeData -- removed, was so 66ish

View File

@ -267,11 +267,11 @@ p2pProtocol eth68(version = ethVersion,
trace trEthSendReplying & "EMPTY PooledTransactions (0x0a)", peer,
sent=0, requested=txHashes.len
await response.send(txs.get)
await response.send(RawRlp(txs.get.toBytes(ctx.chainId)))
# User message 0x0a: PooledTransactions.
proc pooledTransactions(
peer: Peer, transactions: openArray[PooledTransaction])
peer: Peer, transactions: RawRlp)
# User message 0x0d: GetNodeData -- removed, was so 66ish
# User message 0x0e: NodeData -- removed, was so 66ish

View File

@ -313,6 +313,7 @@ proc dumpDebuggingMetaData*(vmState: BaseVMState, header: BlockHeader,
}
var metaData = %{
"chainId": %distinctBase(com.chainId),
"blockNumber": %blockNumber.toHex,
"txTraces": traceTransactions(captureCom, header, blockBody),
"stateDump": dumpBlockState(captureCom, header, blockBody),

View File

@ -6,6 +6,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
std/[sequtils, typetraits],
./constants, ./errors, eth/[common, keys], ./utils/utils,
common/evmforks, ./vm_gas_costs
@ -32,40 +33,25 @@ proc intrinsicGas*(tx: Transaction, fork: EVMFork): 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)
result = tx.payload.intrinsicGas(fork)
result = distinctBase(tx.payload.input).intrinsicGas(fork)
if tx.contractCreation:
result = result + gasFees[fork][GasTXCreate]
if fork >= FkShanghai:
# cannot use wordCount here, it will raise unlisted exception
let numWords = toWordSize(tx.payload.len)
let numWords = toWordSize(tx.payload.input.len)
result = result + (gasFees[fork][GasInitcodeWord] * numWords)
if tx.txType > TxLegacy:
result = result + tx.accessList.len * ACCESS_LIST_ADDRESS_COST
if tx.payload.access_list.isSome:
template access_list: untyped = tx.payload.access_list.unsafeGet
result = result + access_list.len * ACCESS_LIST_ADDRESS_COST
var numKeys = 0
for n in tx.accessList:
inc(numKeys, n.storageKeys.len)
for n in access_list:
inc(numKeys, n.storage_keys.len)
result = result + 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)
let sig = Signature.fromRaw(tx.signature.ecdsa_signature)
if sig.isOk:
output = sig[]
return true
@ -77,13 +63,8 @@ proc toSignature*(tx: Transaction): 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
output = tx.signature.from_address
true
proc getSender*(tx: Transaction): EthAddress =
## Raises error on failure to recover public key
@ -92,150 +73,30 @@ proc getSender*(tx: Transaction): EthAddress =
proc getRecipient*(tx: Transaction, sender: EthAddress): EthAddress =
if tx.contractCreation:
result = generateAddress(sender, tx.nonce)
result = generateAddress(sender, tx.payload.nonce)
else:
result = tx.to.get()
result = tx.payload.to.get()
proc validateTxLegacy(tx: Transaction, fork: EVMFork) =
var
vMin = 27'i64
vMax = 28'i64
if tx.V >= EIP155_CHAIN_ID_OFFSET:
let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
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
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 legacy transaction")
proc validateTxEip2930(tx: Transaction) =
var isValid = tx.V == 0'i64 or tx.V == 1'i64
isValid = isValid and tx.S >= UInt256.one
isValid = isValid and tx.S < SECPK1_N
isValid = isValid and tx.R < SECPK1_N
if not isValid:
raise newException(ValidationError, "Invalid typed transaction")
proc validateTxEip4844(tx: Transaction) =
validateTxEip2930(tx)
var isValid = tx.payload.len <= MAX_CALLDATA_SIZE
isValid = isValid and tx.accessList.len <= MAX_ACCESS_LIST_SIZE
for acl in tx.accessList:
isValid = isValid and
(acl.storageKeys.len <= MAX_ACCESS_LIST_STORAGE_KEYS)
isValid = isValid and
tx.versionedHashes.len <= MAX_BLOBS_PER_BLOCK
for bv in tx.versionedHashes:
isValid = isValid and
bv.data[0] == VERSIONED_HASH_VERSION_KZG
if not isValid:
raise newException(ValidationError, "Invalid EIP-4844 transaction")
proc validate*(tx: Transaction, fork: EVMFork) =
proc validate*(tx: Transaction, fork: EVMFork, chainId: ChainId) =
# parameters pass validation rules
if tx.intrinsicGas(fork) > tx.gasLimit:
if tx.intrinsicGas(fork).uint64 > tx.payload.gas:
raise newException(ValidationError, "Insufficient gas")
if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
if fork >= FkShanghai and tx.contractCreation and
tx.payload.input.len > EIP3860_MAX_INITCODE_SIZE:
raise newException(ValidationError, "Initcode size exceeds max")
if tx.payload.blob_versioned_hashes.isSome:
template vhs: untyped = tx.payload.blob_versioned_hashes.unsafeGet
if vhs.len > MAX_BLOBS_PER_BLOCK:
raise newException(ValidationError, "Too many blob versioned hashes")
if not vhs.allIt(it.data[0] == VERSIONED_HASH_VERSION_KZG):
raise newException(ValidationError, "Invalid blob versioned hash")
# check signature validity
var sender: EthAddress
if not tx.getSender(sender):
raise newException(ValidationError, "Invalid signature or failed message verification")
case tx.txType
of TxLegacy:
validateTxLegacy(tx, fork)
of TxEip4844:
validateTxEip4844(tx)
of TxEip2930, TxEip1559:
validateTxEip2930(tx)
proc signTransaction*(tx: Transaction, privateKey: PrivateKey, chainId: ChainId, eip155: bool): Transaction =
result = tx
if eip155:
# trigger rlpEncodeEIP155 in nim-eth
result.V = chainId.int64 * 2'i64 + 35'i64
let
rlpTx = rlpEncode(result)
sig = sign(privateKey, rlpTx).toRaw
case tx.txType
of TxLegacy:
if eip155:
result.V = sig[64].int64 + result.V
else:
result.V = sig[64].int64 + 27'i64
else:
result.V = sig[64].int64
result.R = UInt256.fromBytesBE(sig[0..31])
result.S = UInt256.fromBytesBE(sig[32..63])
# deriveChainId derives the chain id from the given v parameter
func deriveChainId*(v: int64, chainId: ChainId): ChainId =
if v == 27 or v == 28:
chainId
else:
((v - 35) div 2).ChainId
func validateChainId*(tx: Transaction, chainId: ChainId): bool =
if tx.txType == TxLegacy:
chainId.uint64 == deriveChainId(tx.V, chainId).uint64
else:
chainId.uint64 == tx.chainId.uint64
func eip1559TxNormalization*(tx: Transaction;
baseFee: GasInt): Transaction =
## This function adjusts a legacy transaction to EIP-1559 standard. This
## is needed particularly when using the `validateTransaction()` utility
## with legacy transactions.
result = tx
if tx.txType < TxEip1559:
result.maxPriorityFee = tx.gasPrice
result.maxFee = tx.gasPrice
else:
result.gasPrice = baseFee + min(result.maxPriorityFee, result.maxFee - baseFee)
func effectiveGasTip*(tx: Transaction; baseFee: Option[UInt256]): GasInt =
var
maxPriorityFee = tx.maxPriorityFee
maxFee = tx.maxFee
baseFee = baseFee.get(0.u256).truncate(GasInt)
if tx.txType < TxEip1559:
maxPriorityFee = tx.gasPrice
maxFee = tx.gasPrice
min(maxPriorityFee, maxFee - baseFee)
proc decodeTx*(bytes: openArray[byte]): Transaction =
var rlp = rlpFromBytes(bytes)
result = rlp.read(Transaction)
if rlp.hasData:
raise newException(RlpError, "rlp: input contains more than one value")
proc decodePooledTx*(bytes: openArray[byte]): PooledTransaction =
var rlp = rlpFromBytes(bytes)
result = rlp.read(PooledTransaction)
if rlp.hasData:
raise newException(RlpError, "rlp: input contains more than one value")
let anyTx = AnyTransaction.fromOneOfBase(tx).valueOr:
raise newException(ValidationError, "Invalid combination of fields")
withTxVariant(anyTx):
if txVariant.validate_transaction(chainId).isErr:
raise newException(ValidationError,
"Invalid signature or failed message verification")

View File

@ -9,6 +9,7 @@
{.push raises: [].}
import
std/typetraits,
eth/common/eth_types, stint, options, stew/ptrops,
chronos,
".."/[vm_types, vm_state, vm_computation, vm_state_transactions],
@ -124,7 +125,7 @@ proc initialAccessListEIP2929(call: CallParams) =
# EIP2930 optional access list.
for account in call.accessList:
db.accessList(account.address)
for key in account.storageKeys:
for key in distinctBase(account.storageKeys):
db.accessList(account.address, UInt256.fromBytesBE(key))
proc setupHost(call: CallParams): TransactionHost =

View File

@ -153,40 +153,42 @@ proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState,
result = CallParams(
vmState: vmState,
forkOverride: some(fork),
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
gasPrice: tx.payload.max_fee_per_gas.truncate(int64).GasInt,
gasLimit: tx.payload.gas.int64,
sender: sender,
to: tx.destination,
to: tx.payload.to.get(default(EthAddress)),
isCreate: tx.contractCreation,
value: tx.value,
input: tx.payload
value: tx.payload.value,
input: distinctBase(tx.payload.input)
)
if tx.txType > TxLegacy:
result.accessList = tx.accessList
if tx.payload.access_list.isSome:
result.accessList = tx.payload.access_list.unsafeGet
if tx.txType >= TxEip4844:
result.versionedHashes = tx.versionedHashes
if tx.payload.blob_versioned_hashes.isSome:
result.versionedHashes =
distinctBase(tx.payload.blob_versioned_hashes.unsafeGet)
proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams =
result = CallParams(
vmState: vmState,
forkOverride: some(fork),
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
gasPrice: tx.payload.max_fee_per_gas.truncate(int64).GasInt,
gasLimit: tx.payload.gas.int64,
sender: sender,
to: tx.destination,
to: tx.payload.to.get(default(EthAddress)),
isCreate: tx.contractCreation,
value: tx.value,
input: tx.payload,
value: tx.payload.value,
input: distinctBase(tx.payload.input),
noIntrinsic: true, # Don't charge intrinsic gas.
noRefund: true, # Don't apply gas refund/burn rule.
)
if tx.txType > TxLegacy:
result.accessList = tx.accessList
if tx.payload.access_list.isSome:
result.accessList = tx.payload.access_list.unsafeGet
if tx.txType >= TxEip4844:
result.versionedHashes = tx.versionedHashes
if tx.payload.blob_versioned_hashes.isSome:
result.versionedHashes =
distinctBase(tx.payload.blob_versioned_hashes.unsafeGet)
proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): GasInt
{.gcsafe, raises: [CatchableError].} =

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

@ -69,7 +69,8 @@ proc main() =
let
blockEnv = json.parseFile(paramStr(1))
memoryDB = newCoreDbRef(LegacyDbMemory)
chainId = blockEnv["chainId"].getInt().ChainId
memoryDB = newCoreDbRef(LegacyDbMemory, chainId)
blockNumber = UInt256.fromHex(blockEnv["blockNumber"].getStr())
prepareBlockEnv(blockEnv, memoryDB)

View File

@ -49,12 +49,13 @@ proc request*(
proc requestBlockBody(
n: JsonNode,
blockNumber: BlockNumber,
chainId: ChainId,
client: Option[RpcClient] = none[RpcClient]()): BlockBody =
let txs = n["transactions"]
if txs.len > 0:
result.transactions = newSeqOfCap[Transaction](txs.len)
for tx in txs:
let txn = parseTransaction(tx)
let txn = parseTransaction(tx, chainId)
validateTxSenderAndHash(tx, txn)
result.transactions.add txn
@ -108,12 +109,13 @@ proc requestHeader*(
proc requestBlock*(
blockNumber: BlockNumber,
chainId: ChainId,
flags: set[DownloadFlags] = {},
client: Option[RpcClient] = none[RpcClient]()): Block =
let header = requestHeader(blockNumber, client)
result.jsonData = header
result.header = parseBlockHeader(header)
result.body = requestBlockBody(header, blockNumber, client)
result.body = requestBlockBody(header, blockNumber, chainId, client)
if DownloadTxTrace in flags:
result.traces = requestTxTraces(header, client)

View File

@ -46,8 +46,12 @@ proc dumpDebug(com: CommonRef, blockNumber: UInt256) =
vmState.dumpDebuggingMetaData(header, body, false)
proc main() {.used.} =
let conf = getConfiguration()
let com = CommonRef.new(newCoreDbRef(LegacyDbPersistent, conf.dataDir), false)
let
conf = getConfiguration()
params = networkParams(conf.netId)
com = CommonRef.new(
newCoreDbRef(LegacyDbPersistent, conf.dataDir, params.config.chainId),
false)
if conf.head != 0.u256:
dumpDebug(com, conf.head)

View File

@ -35,10 +35,11 @@ proc parseAddress(address: string): EthAddress =
proc parseU256(val: string): UInt256 =
UInt256.fromHex(val)
proc prepareBlockEnv(parent: BlockHeader, thisBlock: Block): CoreDbRef =
proc prepareBlockEnv(
parent: BlockHeader, thisBlock: Block, chainId: ChainId): CoreDbRef =
var
accounts = requestPostState(thisBlock)
memoryDB = newCoreDbRef LegacyDbMemory
accounts = requestPostState(thisBlock, chainId)
memoryDB = newCoreDbRef(LegacyDbMemory, chainId)
accountDB = newAccountStateDB(memoryDB, parent.stateRoot, false)
parentNumber = %(parent.blockNumber.prefixHex)
@ -95,13 +96,14 @@ proc putAncestorsIntoDB(vmState: HunterVMState, db: CoreDbRef) =
for header in vmState.headers.values:
db.addBlockNumberToHashLookup(header)
proc huntProblematicBlock(blockNumber: UInt256): ValidationResult =
proc huntProblematicBlock(
blockNumber: UInt256, chainId: ChainId): ValidationResult =
let
# prepare needed state from previous block
parentNumber = blockNumber - 1
thisBlock = requestBlock(blockNumber)
parentBlock = requestBlock(parentNumber)
memoryDB = prepareBlockEnv(parentBlock.header, thisBlock)
thisBlock = requestBlock(blockNumber, chainId)
parentBlock = requestBlock(parentNumber, chainId)
memoryDB = prepareBlockEnv(parentBlock.header, thisBlock, chainId)
# try to execute current block
com = CommonRef.new(memoryDB, false)
@ -122,7 +124,10 @@ proc huntProblematicBlock(blockNumber: UInt256): ValidationResult =
result = validationResult
proc main() {.used.} =
let conf = getConfiguration()
let
conf = getConfiguration()
params = networkParams(conf.netId)
chainId = params.config.chainId
if conf.head == 0.u256:
echo "please specify the starting block with `--head:blockNumber`"
@ -138,7 +143,7 @@ proc main() {.used.} =
while true:
echo blockNumber
if huntProblematicBlock(blockNumber) != ValidationResult.OK:
if huntProblematicBlock(blockNumber, chainId) != ValidationResult.OK:
echo "shot down problematic block: ", blockNumber
problematicBlocks.add blockNumber
blockNumber = blockNumber + 1

View File

@ -9,9 +9,9 @@
# according to those terms.
import
json, strutils, options, os,
std/[json, options, os, strutils, typetraits],
eth/common, httputils, nimcrypto/utils,
stint, stew/byteutils
results, stint, stew/byteutils
import ../nimbus/transaction, ../nimbus/utils/ec_recover
@ -96,15 +96,36 @@ proc fromJson*(n: JsonNode, name: string, x: var SomeInteger) =
x = T(node.getInt)
doAssert($x == $node.getInt, name)
func fromJson(n: JsonNode, name: string, x: var ChainId) =
n.fromJson(name, distinctBase(x))
proc fromJson*(n: JsonNode, name: string, x: var EthTime) =
x = EthTime(hexToInt(n[name].getStr(), uint64))
doAssert(x.uint64.prefixHex == toLowerAscii(n[name].getStr()), name)
proc fromJson*[T](n: JsonNode, name: string, x: var Option[T]) =
if name in n:
if name in n and n[name].kind != JNull:
var val: T
n.fromJson(name, val)
x = some(val)
x = options.some(val)
else:
x = options.none(T)
func fromJson*[T](n: JsonNode, name: string, x: var Opt[T]) =
if name in n and n[name].kind != JNull:
var val: T
n.fromJson(name, val)
x.ok val
else:
x.err()
func fromJson*[E, N](n: JsonNode, name: string, x: var List[E, N]) =
var v: seq[E]
n.fromJson(name, v)
if v.len > N:
raise (ref ValueError)(msg:
"List[" & $E & ", Limit " & $N & "] cannot fit " & $v.len & " items")
x = List[E, N].init(v)
proc fromJson*(n: JsonNode, name: string, x: var TxType) =
let node = n[name]
@ -121,6 +142,20 @@ proc fromJson*(n: JsonNode, name: string, x: var seq[Hash256]) =
hexToByteArray(v.getStr(), h.data)
x.add h
func fromJson*(n: JsonNode, name: string, x: var common.AccessList) =
x.reset()
let node = n[name]
for innerNode in node:
var entry: common.AccessPair
innerNode.fromJson "address", entry.address
for storageKeyVal in innerNode["storageKeys"]:
var storageKey: common.StorageKey
hexToByteArray(storageKeyVal.getStr(), storageKey)
if not entry.storage_keys.add storageKey:
raise (ref ValueError)(msg: "StorageKeys capacity exceeded")
if not x.add entry:
raise (ref ValueError)(msg: "AccessList capacity exceeded")
proc parseBlockHeader*(n: JsonNode): BlockHeader =
n.fromJson "parentHash", result.parentHash
n.fromJson "sha3Uncles", result.ommersHash
@ -147,53 +182,72 @@ proc parseBlockHeader*(n: JsonNode): BlockHeader =
# probably geth bug
result.fee = none(UInt256)
proc parseAccessPair(n: JsonNode): AccessPair =
n.fromJson "address", result.address
let keys = n["storageKeys"]
for kn in keys:
result.storageKeys.add hexToByteArray[32](kn.getStr())
proc parseTransaction*(n: JsonNode): Transaction =
var tx = Transaction(txType: TxLegacy)
proc parseTransaction*(n: JsonNode, chain_id: ChainId): Transaction =
var
tx: TransactionPayload
gasPrice: Opt[UInt256]
txChainId: Opt[ChainId]
v: UInt256
r: UInt256
s: UInt256
n.fromJson "nonce", tx.nonce
n.fromJson "gasPrice", tx.gasPrice
n.fromJson "gas", tx.gasLimit
if n["to"].kind != JNull:
var to: EthAddress
n.fromJson "to", to
tx.to = some(to)
n.fromJson "gasPrice", gasPrice
n.fromJson "gas", tx.gas
n.fromJson "to", tx.to
n.fromJson "value", tx.value
n.fromJson "input", tx.payload
n.fromJson "v", tx.V
n.fromJson "r", tx.R
n.fromJson "s", tx.S
if n.hasKey("type") and n["type"].kind != JNull:
n.fromJson "type", tx.txType
if tx.txType >= TxEip1559:
n.fromJson "maxPriorityFeePerGas", tx.maxPriorityFee
n.fromJson "maxFeePerGas", tx.maxFee
if tx.txType >= TxEip2930:
if n.hasKey("chainId"):
let id = hexToInt(n["chainId"].getStr(), int)
tx.chainId = ChainId(id)
let accessList = n["accessList"]
if accessList.len > 0:
for acn in accessList:
tx.accessList.add parseAccessPair(acn)
if tx.txType >= TxEip4844:
n.fromJson "maxFeePerBlobGas", tx.maxFeePerBlobGas
if n.hasKey("versionedHashes") and n["versionedHashes"].kind != JNull:
n.fromJson "versionedHashes", tx.versionedHashes
tx
n.fromJson "input", tx.input
n.fromJson "v", v
n.fromJson "r", r
n.fromJson "s", s
n.fromJson "chainId", txChainId
n.fromJson "type", tx.tx_type
n.fromJson "accessList", tx.access_list
n.fromJson "maxPriorityFeePerGas", tx.max_priority_fee_per_gas
n.fromJson "maxFeePerGas", tx.max_fee_per_gas
n.fromJson "maxFeePerBlobGas", tx.max_fee_per_blob_gas
n.fromJson "versionedHashes", tx.blob_versioned_hashes
if gasPrice.get(tx.max_fee_per_gas) != tx.max_fee_per_gas:
raise (ref ValueError)(msg: "`gasPrice` and `maxFeePerGas` don't match")
if tx.tx_type.get(TxLegacy) != TxLegacy and txChainId.isNone:
raise (ref ValueError)(msg: "`chainId` is required")
if tx.tx_type == Opt.some(TxLegacy) and txChainId.isNone:
tx.tx_type.reset()
if txChainId.get(chain_id) != chain_id:
raise (ref ValueError)(msg: "Unsupported `chainId`")
if r >= SECP256K1N:
raise (ref ValueError)(msg: "Invalid `r`")
if s < UInt256.one or s >= SECP256K1N:
raise (ref ValueError)(msg: "Invalid `s`")
let anyTx = AnyTransactionPayload.fromOneOfBase(tx).valueOr:
raise (ref ValueError)(msg: "Invalid combination of fields")
withTxPayloadVariant(anyTx):
let y_parity =
when txKind == TransactionKind.Replayable:
if v == 27.u256:
false
elif v == 28.u256:
true
else:
raise (ref ValueError)(msg: "Invalid `v`")
elif txKind == TransactionKind.Legacy:
let
res = v.isEven
expected_v =
distinctBase(chain_id).u256 * 2 + (if res: 36.u256 else: 35.u256)
if v != expected_v:
raise (ref ValueError)(msg: "Invalid `v`")
res
else:
if v > UInt256.one:
raise (ref ValueError)(msg: "Invalid `v`")
v.isOdd
var signature: TransactionSignature
signature.ecdsa_signature = ecdsa_pack_signature(y_parity, r, s)
signature.from_address = ecdsa_recover_from_address(
signature.ecdsa_signature,
txPayloadVariant.compute_sig_hash(chain_id)).valueOr:
raise (ref ValueError)(msg: "Cannot compute `from` address")
Transaction(payload: tx, signature: signature)
proc parseWithdrawal*(n: JsonNode): Withdrawal =
n.fromJson "index", result.index

View File

@ -52,14 +52,17 @@ proc main() {.used.} =
# 52029 first block with receipts logs
# 66407 failed transaction
let conf = configuration.getConfiguration()
let com = CommonRef.new(
newCoreDbRef(LegacyDbPersistent, conf.dataDir),
false, conf.netId, networkParams(conf.netId))
let
conf = configuration.getConfiguration()
params = networkParams(conf.netId)
com = CommonRef.new(
newCoreDbRef(LegacyDbPersistent, conf.dataDir, params.config.chainId),
false, conf.netId, networkParams(conf.netId))
# move head to block number ...
if conf.head != 0.u256:
var parentBlock = requestBlock(conf.head, { DownloadAndValidate })
var parentBlock = requestBlock(
conf.head, com.chainId, { DownloadAndValidate })
discard com.db.setHead(parentBlock.header)
if canonicalHeadHashKey().toOpenArray notin com.db.kvt:
@ -86,7 +89,8 @@ proc main() {.used.} =
var thisBlock: Block
try:
thisBlock = requestBlock(blockNumber, { DownloadAndValidate })
thisBlock = requestBlock(
blockNumber, com.chainId, { DownloadAndValidate })
except CatchableError as e:
if retryCount < 3:
warn "Unable to get block data via JSON-RPC API", error = e.msg

View File

@ -48,12 +48,14 @@ proc main() =
try:
let
nimbus = json.parseFile(paramStr(1))
chainId = nimbus["chainId"].getInt().ChainId
blockNumber = UInt256.fromHex(nimbus["blockNumber"].getStr())
thisBlock = requestBlock(blockNumber, {DownloadReceipts, DownloadTxTrace})
accounts = requestPostState(thisBlock)
thisBlock = requestBlock(
blockNumber, chainId, {DownloadReceipts, DownloadTxTrace})
accounts = requestPostState(thisBlock, chainId)
geth = generateGethData(thisBlock, blockNumber, accounts)
parentNumber = blockNumber - 1.u256
parentBlock = requestBlock(parentNumber)
parentBlock = requestBlock(parentNumber, chainId)
processNimbusData(nimbus)

View File

@ -86,7 +86,7 @@ proc hasInternalTx(tx: Transaction, blockNumber: UInt256, sender: EthAddress): b
recipientHasCode = code.getStr.len > 2 # "0x"
if tx.contractCreation:
return recipientHasCode or tx.payload.len > 0
return recipientHasCode or tx.payload.input.len > 0
recipientHasCode
@ -143,7 +143,8 @@ proc updateAccount*(address: string, account: JsonNode, blockNumber: UInt256) =
x["value"] = padding(x["value"].getStr())
account["storage"][x["key"].getStr] = x["value"]
proc requestPostState*(premix, n: JsonNode, blockNumber: UInt256) =
proc requestPostState*(
premix, n: JsonNode, blockNumber: UInt256, chainId: ChainId) =
type
TxKind {.pure.} = enum
Regular
@ -156,7 +157,7 @@ proc requestPostState*(premix, n: JsonNode, blockNumber: UInt256) =
let tracer = jsonTracer(postStateTracer)
for t in txs:
var txKind = TxKind.Regular
let tx = parseTransaction(t)
let tx = parseTransaction(t, chainId)
let sender = tx.getSender
if tx.contractCreation: txKind = TxKind.ContractCreation
if hasInternalTx(tx, blockNumber, sender):
@ -171,11 +172,11 @@ proc requestPostState*(premix, n: JsonNode, blockNumber: UInt256) =
t["txKind"] = %($txKind)
proc requestPostState*(thisBlock: Block): JsonNode =
proc requestPostState*(thisBlock: Block, chainId: ChainId): JsonNode =
let blockNumber = thisBlock.header.blockNumber
var premix = newJArray()
premix.requestPostState(thisBlock.jsonData, blockNumber)
premix.requestPostState(thisBlock.jsonData, blockNumber, chainId)
premix.requestAccount(blockNumber, thisBlock.header.coinbase)
for uncle in thisBlock.body.uncles:
premix.requestAccount(blockNumber, uncle.coinbase)

View File

@ -9,17 +9,19 @@
# according to those terms.
import
std/typetraits,
json, stint, stew/byteutils,
../nimbus/db/[core_db, storage_types], eth/[rlp, common],
../nimbus/tracer
proc generatePrestate*(nimbus, geth: JsonNode, blockNumber: UInt256, parent, header: BlockHeader, body: BlockBody) =
let
chainId = nimbus["chainId"].getInt().ChainId
state = nimbus["state"]
headerHash = rlpHash(header)
var
chainDB = newCoreDbRef(LegacyDbMemory)
chainDB = newCoreDbRef(LegacyDbMemory, chainId)
discard chainDB.setHead(parent, true)
discard chainDB.persistTransactions(blockNumber, body.transactions)
@ -34,6 +36,7 @@ proc generatePrestate*(nimbus, geth: JsonNode, blockNumber: UInt256, parent, hea
chainDB.kvt.put(key, value)
var metaData = %{
"chainId": %distinctBase(chainId),
"blockNumber": %blockNumber.toHex,
"geth": geth
}

View File

@ -52,7 +52,10 @@ proc validateBlock(com: CommonRef, blockNumber: BlockNumber): BlockNumber =
proc main() {.used.} =
let
conf = getConfiguration()
com = CommonRef.new(newCoreDbRef(LegacyDbPersistent, conf.dataDir), false)
params = networkParams(conf.netId)
com = CommonRef.new(
newCoreDbRef(LegacyDbPersistent, conf.dataDir, params.config.chainId),
false)
# move head to block number ...
if conf.head == 0.u256:

View File

@ -54,11 +54,12 @@ proc buildAccountsTableFromKeys(
proc verifyWitness*(
trustedStateRoot: KeccakHash,
witness: BlockWitness,
flags: WitnessFlags): Result[TableRef[EthAddress, AccountData], string] =
flags: WitnessFlags,
chainId: ChainId): Result[TableRef[EthAddress, AccountData], string] =
if witness.len() == 0:
return err("witness is empty")
let db = newCoreDbRef(LegacyDbMemory)
let db = newCoreDbRef(LegacyDbMemory, chainId)
var tb = initTreeBuilder(witness, db, flags)
try:

View File

@ -57,9 +57,12 @@ proc main() {.used.} =
# nimbus --rpcapi: eth, debug --prune: archive
var conf = makeConfig()
let db = newCoreDbRef(DefaultDbPersistent, string conf.dataDir)
let com = CommonRef.new(db, false)
let
conf = makeConfig()
params = networkParams(conf.networkId)
db = newCoreDbRef(
DefaultDbPersistent, string conf.dataDir, params.config.chainId)
com = CommonRef.new(db, false)
com.dumpTest(97)
com.dumpTest(98) # no uncles and no tx

View File

@ -9,6 +9,7 @@
# according to those terms.
import
std/typetraits,
json,
../nimbus/common/common, # must be early (compilation annoyance)
../nimbus/db/core_db/persistent,
@ -32,6 +33,7 @@ proc dumpTest(com: CommonRef, blockNumber: int) =
receipts = dumpReceipts(captureCom.db, header)
var metaData = %{
"chainId": %distinctBase(com.chainId),
"blockNumber": %blockNumber.toHex,
"txTraces": txTrace,
"stateDump": stateDump,
@ -56,9 +58,12 @@ proc main() {.used.} =
# nimbus --rpc-api: eth, debug --prune: archive
var conf = makeConfig()
let db = newCoreDbRef(LegacyDbPersistent, string conf.dataDir)
let com = CommonRef.new(db, false)
let
conf = makeConfig()
params = networkParams(conf.networkId)
db = newCoreDbRef(
LegacyDbPersistent, string conf.dataDir, params.config.chainId)
com = CommonRef.new(db, false)
com.dumpTest(97)
com.dumpTest(46147)

2
vendor/nim-eth vendored

@ -1 +1 @@
Subproject commit 34adff98a6c5ae9d5a59bd8859a848b1cb0ae80d
Subproject commit 24ed2afba77249546d3c41485d7d37241681ad35

@ -1 +1 @@
Subproject commit 248f2bdca2d65ff920920c72b764d0622d522596
Subproject commit 338a47f5664868a3a9183d93fd46dbad9d183ff6

2
vendor/nim-web3 vendored

@ -1 +1 @@
Subproject commit de87f860874be944cdc3dfd08765c687fff736c4
Subproject commit 77bfa128f316c1959b92b2c4e7041c861e3ef2e6

2
vendor/nimbus-eth2 vendored

@ -1 +1 @@
Subproject commit fc9bc1da3ae7dde04f4591eba302f9e8b20c3924
Subproject commit 9202e336e4427ee16dd7ca9da4a0f93379b08b22