341 lines
11 KiB
Nim
341 lines
11 KiB
Nim
import
|
|
std/[typetraits, times],
|
|
nimcrypto/sysrand,
|
|
eth/[common, rlp, keys],
|
|
json_rpc/[rpcclient],
|
|
../../../nimbus/transaction,
|
|
../../../nimbus/utils/utils,
|
|
../../../nimbus/beacon/execution_types,
|
|
../../../nimbus/beacon/web3_eth_conv
|
|
|
|
type
|
|
ExecutableData* = object
|
|
parentHash* : common.Hash256
|
|
feeRecipient* : EthAddress
|
|
stateRoot* : common.Hash256
|
|
receiptsRoot* : common.Hash256
|
|
logsBloom* : BloomFilter
|
|
prevRandao* : common.Hash256
|
|
number* : uint64
|
|
gasLimit* : GasInt
|
|
gasUsed* : GasInt
|
|
timestamp* : EthTime
|
|
extraData* : common.Blob
|
|
baseFeePerGas*: UInt256
|
|
blockHash* : common.Hash256
|
|
transactions* : seq[Transaction]
|
|
withdrawals* : Option[seq[Withdrawal]]
|
|
blobGasUsed* : Option[uint64]
|
|
excessBlobGas*: Option[uint64]
|
|
|
|
CustomPayload* = object
|
|
parentHash* : Option[common.Hash256]
|
|
feeRecipient* : Option[EthAddress]
|
|
stateRoot* : Option[common.Hash256]
|
|
receiptsRoot* : Option[common.Hash256]
|
|
logsBloom* : Option[BloomFilter]
|
|
prevRandao* : Option[common.Hash256]
|
|
number* : Option[uint64]
|
|
gasLimit* : Option[GasInt]
|
|
gasUsed* : Option[GasInt]
|
|
timestamp* : Option[EthTime]
|
|
extraData* : Option[common.Blob]
|
|
baseFeePerGas*: Option[UInt256]
|
|
blockHash* : Option[common.Hash256]
|
|
transactions* : Option[seq[Transaction]]
|
|
withdrawals* : Option[seq[Withdrawal]]
|
|
blobGasUsed* : Option[uint64]
|
|
excessBlobGas*: Option[uint64]
|
|
beaconRoot* : Option[common.Hash256]
|
|
removeWithdrawals*: bool
|
|
|
|
InvalidPayloadField* = enum
|
|
InvalidParentHash
|
|
InvalidStateRoot
|
|
InvalidReceiptsRoot
|
|
InvalidNumber
|
|
InvalidGasLimit
|
|
InvalidGasUsed
|
|
InvalidTimestamp
|
|
InvalidPrevRandao
|
|
RemoveTransaction
|
|
InvalidTransactionSignature
|
|
InvalidTransactionNonce
|
|
InvalidTransactionGas
|
|
InvalidTransactionGasPrice
|
|
InvalidTransactionValue
|
|
|
|
SignatureVal = object
|
|
V: int64
|
|
R: UInt256
|
|
S: UInt256
|
|
|
|
CustomTx = object
|
|
nonce : Option[AccountNonce]
|
|
gasPrice: Option[GasInt]
|
|
gasLimit: Option[GasInt]
|
|
to : Option[EthAddress]
|
|
value : Option[UInt256]
|
|
data : Option[seq[byte]]
|
|
sig : Option[SignatureVal]
|
|
|
|
proc customizePayload*(basePayload: ExecutableData, customData: CustomPayload): ExecutionPayload =
|
|
let txs = if customData.transactions.isSome:
|
|
customData.transactions.get
|
|
else:
|
|
basePayload.transactions
|
|
let txRoot = calcTxRoot(txs)
|
|
|
|
let wdRoot = if customData.withdrawals.isSome:
|
|
some(calcWithdrawalsRoot(customData.withdrawals.get))
|
|
elif basePayload.withdrawals.isSome:
|
|
some(calcWithdrawalsRoot(basePayload.withdrawals.get))
|
|
else:
|
|
none(common.Hash256)
|
|
|
|
var customHeader = common.BlockHeader(
|
|
parentHash: basePayload.parentHash,
|
|
ommersHash: EMPTY_UNCLE_HASH,
|
|
coinbase: basePayload.feeRecipient,
|
|
stateRoot: basePayload.stateRoot,
|
|
txRoot: txRoot,
|
|
receiptRoot: basePayload.receiptsRoot,
|
|
bloom: basePayload.logsBloom,
|
|
difficulty: 0.u256,
|
|
blockNumber: basePayload.number.toBlockNumber,
|
|
gasLimit: basePayload.gasLimit,
|
|
gasUsed: basePayload.gasUsed,
|
|
timestamp: basePayload.timestamp,
|
|
extraData: basePayload.extraData,
|
|
mixDigest: basePayload.prevRandao,
|
|
nonce: default(BlockNonce),
|
|
fee: some(basePayload.baseFeePerGas),
|
|
withdrawalsRoot: wdRoot,
|
|
blobGasUsed: basePayload.blobGasUsed,
|
|
excessBlobGas: basePayload.excessBlobGas,
|
|
)
|
|
|
|
# Overwrite custom information
|
|
if customData.parentHash.isSome:
|
|
customHeader.parentHash = customData.parentHash.get
|
|
|
|
if customData.feeRecipient.isSome:
|
|
customHeader.coinbase = customData.feeRecipient.get
|
|
|
|
if customData.stateRoot.isSome:
|
|
customHeader.stateRoot = customData.stateRoot.get
|
|
|
|
if customData.receiptsRoot.isSome:
|
|
customHeader.receiptRoot = customData.receiptsRoot.get
|
|
|
|
if customData.logsBloom.isSome:
|
|
customHeader.bloom = customData.logsBloom.get
|
|
|
|
if customData.prevRandao.isSome:
|
|
customHeader.mixDigest = customData.prevRandao.get
|
|
|
|
if customData.number.isSome:
|
|
customHeader.blockNumber = toBlockNumber(customData.number.get)
|
|
|
|
if customData.gasLimit.isSome:
|
|
customHeader.gasLimit = customData.gasLimit.get
|
|
|
|
if customData.gasUsed.isSome:
|
|
customHeader.gasUsed = customData.gasUsed.get
|
|
|
|
if customData.timestamp.isSome:
|
|
customHeader.timestamp = customData.timestamp.get
|
|
|
|
if customData.extraData.isSome:
|
|
customHeader.extraData = customData.extraData.get
|
|
|
|
if customData.baseFeePerGas.isSome:
|
|
customHeader.baseFee = customData.baseFeePerGas.get
|
|
|
|
if customData.blobGasUsed.isSome:
|
|
customHeader.blobGasUsed = customData.blobGasUsed
|
|
|
|
if customData.excessBlobGas.isSome:
|
|
customHeader.excessBlobGas = customData.excessBlobGas
|
|
|
|
if customData.beaconRoot.isSome:
|
|
customHeader.parentBeaconBlockRoot = customData.beaconRoot
|
|
|
|
# Return the new payload
|
|
result = ExecutionPayload(
|
|
parentHash: w3Hash customHeader.parentHash,
|
|
feeRecipient: w3Addr customHeader.coinbase,
|
|
stateRoot: w3Hash customHeader.stateRoot,
|
|
receiptsRoot: w3Hash customHeader.receiptRoot,
|
|
logsBloom: w3Bloom customHeader.bloom,
|
|
prevRandao: w3PrevRandao customHeader.mixDigest,
|
|
blockNumber: w3Qty customHeader.blockNumber,
|
|
gasLimit: w3Qty customHeader.gasLimit,
|
|
gasUsed: w3Qty customHeader.gasUsed,
|
|
timestamp: w3Qty customHeader.timestamp,
|
|
extraData: w3ExtraData customHeader.extraData,
|
|
baseFeePerGas: customHeader.baseFee,
|
|
blockHash: w3Hash customHeader.blockHash,
|
|
blobGasUsed: w3Qty customHeader.blobGasUsed,
|
|
excessBlobGas: w3Qty customHeader.excessBlobGas,
|
|
)
|
|
|
|
for tx in txs:
|
|
let txData = rlp.encode(tx)
|
|
result.transactions.add TypedTransaction(txData)
|
|
|
|
let wds = if customData.withdrawals.isSome:
|
|
customData.withdrawals
|
|
elif basePayload.withdrawals.isSome:
|
|
basePayload.withdrawals
|
|
else:
|
|
none(seq[Withdrawal])
|
|
|
|
if wds.isSome and customData.removeWithdrawals.not:
|
|
result.withdrawals = some(w3Withdrawals(wds.get))
|
|
|
|
proc toExecutableData*(payload: ExecutionPayload): ExecutableData =
|
|
result = ExecutableData(
|
|
parentHash : ethHash payload.parentHash,
|
|
feeRecipient : distinctBase payload.feeRecipient,
|
|
stateRoot : ethHash payload.stateRoot,
|
|
receiptsRoot : ethHash payload.receiptsRoot,
|
|
logsBloom : distinctBase payload.logsBloom,
|
|
prevRandao : ethHash payload.prevRandao,
|
|
number : uint64 payload.blockNumber,
|
|
gasLimit : GasInt payload.gasLimit,
|
|
gasUsed : GasInt payload.gasUsed,
|
|
timestamp : ethTime payload.timestamp,
|
|
extraData : distinctBase payload.extraData,
|
|
baseFeePerGas : payload.baseFeePerGas,
|
|
blockHash : ethHash payload.blockHash,
|
|
blobGasUsed : u64 payload.blobGasUsed,
|
|
excessBlobGas : u64 payload.excessBlobGas,
|
|
transactions : ethTxs payload.transactions,
|
|
withdrawals : ethWithdrawals payload.withdrawals,
|
|
)
|
|
|
|
proc customizePayload*(basePayload: ExecutionPayload, customData: CustomPayload): ExecutionPayload =
|
|
customizePayload(basePayload.toExecutableData, customData)
|
|
|
|
proc customizeTx(baseTx: Transaction, vaultKey: PrivateKey, customTx: CustomTx): Transaction =
|
|
# Create a modified transaction base, from the base transaction and customData mix
|
|
var modTx = Transaction(
|
|
txType : TxLegacy,
|
|
nonce : baseTx.nonce,
|
|
gasPrice: baseTx.gasPrice,
|
|
gasLimit: baseTx.gasLimit,
|
|
to : baseTx.to,
|
|
value : baseTx.value,
|
|
payload : baseTx.payload
|
|
)
|
|
|
|
if customTx.nonce.isSome:
|
|
modTx.nonce = customTx.nonce.get
|
|
|
|
if customTx.gasPrice.isSome:
|
|
modTx.gasPrice = customTx.gasPrice.get
|
|
|
|
if customTx.gasLimit.isSome:
|
|
modTx.gasLimit = customTx.gasLimit.get
|
|
|
|
if customTx.to.isSome:
|
|
modTx.to = customTx.to
|
|
|
|
if customTx.value.isSome:
|
|
modTx.value = customTx.value.get
|
|
|
|
if customTx.data.isSome:
|
|
modTx.payload = customTx.data.get
|
|
|
|
if customTx.sig.isSome:
|
|
let sig = customTx.sig.get
|
|
modTx.V = sig.V
|
|
modTx.R = sig.R
|
|
modTx.S = sig.S
|
|
modTx
|
|
else:
|
|
# If a custom signature was not specified, simply sign the transaction again
|
|
let chainId = baseTx.chainId
|
|
signTransaction(modTx, vaultKey, chainId, eip155 = true)
|
|
|
|
proc modifyHash(x: common.Hash256): common.Hash256 =
|
|
result = x
|
|
result.data[^1] = byte(255 - x.data[^1].int)
|
|
|
|
proc generateInvalidPayload*(basePayload: ExecutableData,
|
|
payloadField: InvalidPayloadField,
|
|
vaultKey: PrivateKey): ExecutionPayload =
|
|
|
|
var customPayload: CustomPayload
|
|
|
|
case payloadField
|
|
of InvalidParentHash:
|
|
customPayload.parentHash = some(modifyHash(basePayload.parentHash))
|
|
of InvalidStateRoot:
|
|
customPayload.stateRoot = some(modifyHash(basePayload.stateRoot))
|
|
of InvalidReceiptsRoot:
|
|
customPayload.receiptsRoot = some(modifyHash(basePayload.receiptsRoot))
|
|
of InvalidNumber:
|
|
customPayload.number = some(basePayload.number - 1'u64)
|
|
of InvalidGasLimit:
|
|
customPayload.gasLimit = some(basePayload.gasLimit * 2)
|
|
of InvalidGasUsed:
|
|
customPayload.gasUsed = some(basePayload.gasUsed - 1)
|
|
of InvalidTimestamp:
|
|
customPayload.timestamp = some(basePayload.timestamp - 1.seconds)
|
|
of InvalidPrevRandao:
|
|
# This option potentially requires a transaction that uses the PREVRANDAO opcode.
|
|
# Otherwise the payload will still be valid.
|
|
var randomHash: common.Hash256
|
|
doAssert randomBytes(randomHash.data) == 32
|
|
customPayload.prevRandao = some(randomHash)
|
|
of RemoveTransaction:
|
|
let emptyTxs: seq[Transaction] = @[]
|
|
customPayload.transactions = some(emptyTxs)
|
|
of InvalidTransactionSignature,
|
|
InvalidTransactionNonce,
|
|
InvalidTransactionGas,
|
|
InvalidTransactionGasPrice,
|
|
InvalidTransactionValue:
|
|
|
|
doAssert(basePayload.transactions.len != 0, "No transactions available for modification")
|
|
|
|
var baseTx = basePayload.transactions[0]
|
|
var customTx: CustomTx
|
|
case payloadField
|
|
of InvalidTransactionSignature:
|
|
let sig = SignatureVal(
|
|
V: baseTx.V,
|
|
R: baseTx.R - 1.u256,
|
|
S: baseTx.S
|
|
)
|
|
customTx.sig = some(sig)
|
|
of InvalidTransactionNonce:
|
|
customTx.nonce = some(baseTx.nonce - 1)
|
|
of InvalidTransactionGas:
|
|
customTx.gasLimit = some(0.GasInt)
|
|
of InvalidTransactionGasPrice:
|
|
customTx.gasPrice = some(0.GasInt)
|
|
of InvalidTransactionValue:
|
|
# Vault account initially has 0x123450000000000000000, so this value should overflow
|
|
customTx.value = some(UInt256.fromHex("0x123450000000000000001"))
|
|
else:
|
|
discard
|
|
|
|
let modTx = customizeTx(baseTx, vaultKey, customTx)
|
|
customPayload.transactions = some(@[modTx])
|
|
|
|
customizePayload(basePayload, customPayload)
|
|
|
|
proc generateInvalidPayload*(basePayload: ExecutionPayload,
|
|
payloadField: InvalidPayloadField,
|
|
vaultKey = default(PrivateKey)): ExecutionPayload =
|
|
generateInvalidPayload(basePayload.toExecutableData, payloadField, vaultKey)
|
|
|
|
proc txInPayload*(payload: ExecutionPayload, txHash: common.Hash256): bool =
|
|
for txBytes in payload.transactions:
|
|
let currTx = rlp.decode(common.Blob txBytes, Transaction)
|
|
if rlpHash(currTx) == txHash:
|
|
return true
|