hive: add ethereum/engine simulator

some test with multiple client still need polishing.
merge test using specially crafted blocks need to be added.
This commit is contained in:
jangko 2022-04-11 17:00:39 +07:00
parent 83bdde55aa
commit 4d126f2461
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
10 changed files with 2207 additions and 1 deletions

View File

@ -0,0 +1,329 @@
import
std/[times, tables],
chronicles,
nimcrypto,
stew/byteutils,
eth/common, chronos,
web3/engine_api_types,
json_rpc/rpcclient,
../../../nimbus/merge/mergeutils,
../../../nimbus/debug,
./engine_client
# Consensus Layer Client Mock used to sync the Execution Clients once the TTD has been reached
type
CLMocker* = ref object
nextFeeRecipient*: EthAddress
nextPayloadID: PayloadID
# PoS Chain History Information
prevRandaoHistory*: Table[uint64, Hash256]
executedPayloadHistory*: Table[uint64, ExecutionPayloadV1]
# Latest broadcasted data using the PoS Engine API
latestFinalizedNumber*: uint64
latestFinalizedHeader*: common.BlockHeader
latestPayloadBuilt* : ExecutionPayloadV1
latestExecutedPayload*: ExecutionPayloadV1
latestForkchoice* : ForkchoiceStateV1
# Merge related
firstPoSBlockNumber : Option[uint64]
ttdReached : bool
client : RpcClient
ttd : DifficultyInt
BlockProcessCallbacks* = object
onPayloadProducerSelected* : proc(): bool {.gcsafe.}
onGetPayloadID* : proc(): bool {.gcsafe.}
onGetPayload* : proc(): bool {.gcsafe.}
onNewPayloadBroadcast* : proc(): bool {.gcsafe.}
onHeadBlockForkchoiceBroadcast* : proc(): bool {.gcsafe.}
onSafeBlockForkchoiceBroadcast* : proc(): bool {.gcsafe.}
onFinalizedBlockForkchoiceBroadcast* : proc(): bool {.gcsafe.}
proc init*(cl: CLMocker, client: RpcClient, ttd: DifficultyInt) =
cl.client = client
cl.ttd = ttd
proc newClMocker*(client: RpcClient, ttd: DifficultyInt): CLMocker =
new result
result.init(client, ttd)
proc waitForTTD*(cl: CLMocker): Future[bool] {.async.} =
let (header, waitRes) = await cl.client.waitForTTD(cl.ttd)
if not waitRes:
error "timeout while waiting for TTD"
return false
cl.latestFinalizedHeader = header
cl.ttdReached = true
let headerHash = BlockHash(common.blockHash(cl.latestFinalizedHeader).data)
cl.latestForkchoice.headBlockHash = headerHash
cl.latestForkchoice.safeBlockHash = headerHash
cl.latestForkchoice.finalizedBlockHash = headerHash
cl.latestFinalizedNumber = cl.latestFinalizedHeader.blockNumber.truncate(uint64)
let res = cl.client.forkchoiceUpdatedV1(cl.latestForkchoice)
if res.isErr:
error "forkchoiceUpdated error", msg=res.error
return false
let s = res.get()
if s.payloadStatus.status != PayloadExecutionStatus.valid:
error "forkchoiceUpdated response",
status=s.payloadStatus.status
return false
return true
proc pickNextPayloadProducer(cl: CLMocker): bool =
let nRes = cl.client.blockNumber()
if nRes.isErr:
error "CLMocker: could not get block number", msg=nRes.error
return false
let lastBlockNumber = nRes.get
if cl.latestFinalizedNumber != lastBlockNumber:
return false
var header: common.BlockHeader
let hRes = cl.client.headerByNumber(lastBlockNumber, header)
if hRes.isErr:
error "CLMocker: Could not get block header", msg=hRes.error
return false
let lastBlockHash = header.blockHash
if cl.latestFinalizedHeader.blockHash != lastBlockHash:
error "CLMocker: Failed to obtain a client on the latest block number"
return false
return true
proc getNextPayloadID(cl: CLMocker): bool =
# Generate a random value for the PrevRandao field
var nextPrevRandao: Hash256
doAssert nimcrypto.randomBytes(nextPrevRandao.data) == 32
let timestamp = Quantity toUnix(cl.latestFinalizedHeader.timestamp + 1.seconds)
let payloadAttributes = PayloadAttributesV1(
timestamp: timestamp,
prevRandao: FixedBytes[32] nextPrevRandao.data,
suggestedFeeRecipient: Address cl.nextFeeRecipient,
)
# Save random value
let number = cl.latestFinalizedHeader.blockNumber.truncate(uint64) + 1
cl.prevRandaoHistory[number] = nextPrevRandao
let res = cl.client.forkchoiceUpdatedV1(cl.latestForkchoice, some(payloadAttributes))
if res.isErr:
error "CLMocker: Could not send forkchoiceUpdatedV1", msg=res.error
return false
let s = res.get()
if s.payloadStatus.status != PayloadExecutionStatus.valid:
error "CLMocker: Unexpected forkchoiceUpdated Response from Payload builder",
status=s.payloadStatus.status
doAssert s.payLoadID.isSome
cl.nextPayloadID = s.payloadID.get()
return true
proc getNextPayload(cl: CLMocker): bool =
let res = cl.client.getPayloadV1(cl.nextPayloadID)
if res.isErr:
error "CLMocker: Could not getPayload",
payloadID=toHex(cl.nextPayloadID)
return false
cl.latestPayloadBuilt = res.get()
let header = toBlockHeader(cl.latestPayloadBuilt)
let blockHash = BlockHash header.blockHash.data
if blockHash != cl.latestPayloadBuilt.blockHash:
error "getNextPayload blockHash mismatch",
expected=cl.latestPayloadBuilt.blockHash.toHex,
get=blockHash.toHex
return false
return true
proc broadcastNewPayload(cl: CLMocker, payload: ExecutionPayloadV1): Result[PayloadStatusV1, string] =
let res = cl.client.newPayloadV1(payload)
return res
proc broadcastNextNewPayload(cl: CLMocker): bool =
let res = cl.broadcastNewPayload(cl.latestPayloadBuilt)
if res.isErr:
error "CLMocker: broadcastNewPayload Error", msg=res.error
return false
let s = res.get()
if s.status == PayloadExecutionStatus.valid:
# The client is synced and the payload was immediately validated
# https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md:
# - If validation succeeds, the response MUST contain {status: VALID, latestValidHash: payload.blockHash}
let blockHash = cl.latestPayloadBuilt.blockHash
if s.latestValidHash.isNone:
error "CLMocker: NewPayload returned VALID status with nil LatestValidHash",
expected=blockHash.toHex
return false
let latestValidHash = s.latestValidHash.get()
if latestValidHash != BlockHash(blockHash):
error "CLMocker: NewPayload returned VALID status with incorrect LatestValidHash",
get=latestValidHash.toHex, expected=blockHash.toHex
return false
elif s.status == PayloadExecutionStatus.accepted:
# The client is not synced but the payload was accepted
# https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md:
# - {status: ACCEPTED, latestValidHash: null, validationError: null} if the following conditions are met:
# the blockHash of the payload is valid
# the payload doesn't extend the canonical chain
# the payload hasn't been fully validated.
let nullHash = BlockHash Hash256().data
let latestValidHash = s.latestValidHash.get(nullHash)
if s.latestValidHash.isSome and latestValidHash != nullHash:
error "CLMocker: NewPayload returned ACCEPTED status with incorrect LatestValidHash",
hash=latestValidHash.toHex
return false
else:
error "CLMocker: broadcastNewPayload Response",
status=s.status
return false
cl.latestExecutedPayload = cl.latestPayloadBuilt
let number = uint64 cl.latestPayloadBuilt.blockNumber
cl.executedPayloadHistory[number] = cl.latestPayloadBuilt
return true
proc broadcastForkchoiceUpdated*(cl: CLMocker,
update: ForkchoiceStateV1): Result[ForkchoiceUpdatedResponse, string] =
let res = cl.client.forkchoiceUpdatedV1(update)
return res
proc broadcastLatestForkchoice(cl: CLMocker): bool =
let res = cl.broadcastForkchoiceUpdated(cl.latestForkchoice)
if res.isErr:
error "CLMocker: broadcastForkchoiceUpdated Error", msg=res.error
return false
let s = res.get()
if s.payloadStatus.status != PayloadExecutionStatus.valid:
error "CLMocker: broadcastForkchoiceUpdated Response",
status=s.payloadStatus.status
return false
return true
proc produceSingleBlock*(cl: CLMocker, cb: BlockProcessCallbacks): bool {.gcsafe.} =
doAssert(cl.ttdReached)
if not cl.pickNextPayloadProducer():
return false
if cb.onPayloadProducerSelected != nil:
if not cb.onPayloadProducerSelected():
return false
if not cl.getNextPayloadID():
return false
if cb.onGetPayloadID != nil:
if not cb.onGetPayloadID():
return false
# Give the client a delay between getting the payload ID and actually retrieving the payload
#time.Sleep(PayloadProductionClientDelay)
if not cl.getNextPayload():
return false
if cb.onGetPayload != nil:
if not cb.onGetPayload():
return false
if not cl.broadcastNextNewPayload():
return false
if cb.onNewPayloadBroadcast != nil:
if not cb.onNewPayloadBroadcast():
return false
# Broadcast forkchoice updated with new HeadBlock to all clients
let blockHash = cl.latestPayloadBuilt.blockHash
cl.latestForkchoice.headBlockHash = blockHash
if not cl.broadcastLatestForkchoice():
return false
if cb.onHeadBlockForkchoiceBroadcast != nil:
if not cb.onHeadBlockForkchoiceBroadcast():
return false
# Broadcast forkchoice updated with new SafeBlock to all clients
cl.latestForkchoice.safeBlockHash = blockHash
if not cl.broadcastLatestForkchoice():
return false
if cb.onSafeBlockForkchoiceBroadcast != nil:
if not cb.onSafeBlockForkchoiceBroadcast():
return false
# Broadcast forkchoice updated with new FinalizedBlock to all clients
cl.latestForkchoice.finalizedBlockHash = blockHash
if not cl.broadcastLatestForkchoice():
return false
# Save the number of the first PoS block
if cl.firstPoSBlockNumber.isNone:
let number = cl.latestFinalizedHeader.blockNumber.truncate(uint64) + 1
cl.firstPoSBlockNumber = some(number)
# Save the header of the latest block in the PoS chain
cl.latestFinalizedNumber = cl.latestFinalizedNumber + 1
# Check if any of the clients accepted the new payload
var newHeader: common.BlockHeader
let res = cl.client.headerByNumber(cl.latestFinalizedNumber, newHeader)
if res.isErr:
error "CLMock ProduceSingleBlock", msg=res.error
return false
let newHash = BlockHash newHeader.blockHash.data
if newHash != cl.latestPayloadBuilt.blockHash:
error "CLMocker: None of the clients accepted the newly constructed payload",
hash=newHash.toHex
return false
cl.latestFinalizedHeader = newHeader
if cb.onFinalizedBlockForkchoiceBroadcast != nil:
if not cb.onFinalizedBlockForkchoiceBroadcast():
return false
return true
# Loop produce PoS blocks by using the Engine API
proc produceBlocks*(cl: CLMocker, blockCount: int, cb: BlockProcessCallbacks): bool {.gcsafe.} =
# Produce requested amount of blocks
for i in 0..<blockCount:
if not cl.produceSingleBlock(cb):
return false
return true
# Check whether a block number is a PoS block
proc isBlockPoS*(cl: CLMocker, bn: common.BlockNumber): bool =
if cl.firstPoSBlockNumber.isNone:
return false
let number = cl.firstPoSBlockNumber.get()
let bn = bn.truncate(uint64)
if number > bn:
return false
return true

View File

@ -0,0 +1,191 @@
import
std/[times, json],
stew/byteutils,
eth/[common, rlp], chronos,
web3/engine_api_types,
json_rpc/rpcclient,
../../../tests/rpcclient/eth_api,
../../../premix/parser,
../../../nimbus/rpc/hexstrings,
../../../premix/parser
import web3/engine_api as web3_engine_api
proc forkchoiceUpdatedV1*(client: RpcClient,
update: ForkchoiceStateV1,
payloadAttributes = none(PayloadAttributesV1)):
Result[ForkchoiceUpdatedResponse, string] =
try:
let res = waitFor client.engine_forkchoiceUpdatedV1(update, payloadAttributes)
return ok(res)
except ValueError as e:
return err(e.msg)
proc getPayloadV1*(client: RpcClient, payloadId: PayloadID): Result[ExecutionPayloadV1, string] =
try:
let res = waitFor client.engine_getPayloadV1(payloadId)
return ok(res)
except ValueError as e:
return err(e.msg)
proc newPayloadV1*(client: RpcClient,
payload: ExecutionPayloadV1):
Result[PayloadStatusV1, string] =
try:
let res = waitFor client.engine_newPayloadV1(payload)
return ok(res)
except ValueError as e:
return err(e.msg)
proc toBlockNumber(n: Option[HexQuantityStr]): common.BlockNumber =
if n.isNone:
return 0.toBlockNumber
toBlockNumber(hexToInt(string n.get, uint64))
proc toBlockNonce(n: Option[HexDataStr]): common.BlockNonce =
if n.isNone:
return default(BlockNonce)
hexToByteArray(string n.get, result)
proc toBaseFeePerGas(n: Option[HexQuantityStr]): Option[UInt256] =
if n.isNone:
return none(UInt256)
some(UInt256.fromHex(string n.get))
proc toBlockHeader(bc: eth_api.BlockObject): common.BlockHeader =
common.BlockHeader(
blockNumber: toBlockNumber(bc.number),
parentHash : bc.parentHash,
nonce : toBlockNonce(bc.nonce),
ommersHash : bc.sha3Uncles,
bloom : BloomFilter bc.logsBloom,
txRoot : bc.transactionsRoot,
stateRoot : bc.stateRoot,
receiptRoot: bc.receiptsRoot,
coinbase : bc.miner,
difficulty : UInt256.fromHex(string bc.difficulty),
extraData : hexToSeqByte(string bc.extraData),
mixDigest : bc.mixHash,
gasLimit : hexToInt(string bc.gasLimit, GasInt),
gasUsed : hexToInt(string bc.gasUsed, GasInt),
timestamp : initTime(hexToInt(string bc.timestamp, int64), 0),
fee : toBaseFeePerGas(bc.baseFeePerGas)
)
proc toTransactions(txs: openArray[JsonNode]): seq[Transaction] =
for x in txs:
result.add parseTransaction(x)
proc waitForTTD*(client: RpcClient,
ttd: DifficultyInt): Future[(common.BlockHeader, bool)] {.async.} =
let period = chronos.seconds(5)
var loop = 0
var emptyHeader: common.BlockHeader
while loop < 5:
let res = await client.eth_getBlockByNumber("latest", false)
if res.isNone:
return (emptyHeader, false)
let bc = res.get()
if hexToInt(string bc.totalDifficulty, int64).u256 >= ttd:
return (toBlockHeader(bc), true)
await sleepAsync(period)
inc loop
return (emptyHeader, false)
proc blockNumber*(client: RpcClient): Result[uint64, string] =
try:
let res = waitFor client.eth_blockNumber()
return ok(hexToInt(string res, uint64))
except ValueError as e:
return err(e.msg)
proc headerByNumber*(client: RpcClient, number: uint64, output: var common.BlockHeader): Result[void, string] =
try:
let qty = encodeQuantity(number)
let res = waitFor client.eth_getBlockByNumber(string qty, false)
if res.isNone:
return err("failed to get blockHeader: " & $number)
output = toBlockHeader(res.get())
return ok()
except ValueError as e:
return err(e.msg)
proc blockByNumber*(client: RpcClient, number: uint64, output: var common.EthBlock): Result[void, string] =
try:
let qty = encodeQuantity(number)
let res = waitFor client.eth_getBlockByNumber(string qty, true)
if res.isNone:
return err("failed to get block: " & $number)
let blk = res.get()
output.header = toBlockHeader(blk)
output.txs = toTransactions(blk.transactions)
return ok()
except ValueError as e:
return err(e.msg)
proc latestHeader*(client: RpcClient, output: var common.BlockHeader): Result[void, string] =
try:
let res = waitFor client.eth_getBlockByNumber("latest", false)
if res.isNone:
return err("failed to get latest blockHeader")
output = toBlockHeader(res.get())
return ok()
except ValueError as e:
return err(e.msg)
proc latestBlock*(client: RpcClient, output: var common.EthBlock): Result[void, string] =
try:
let res = waitFor client.eth_getBlockByNumber("latest", true)
if res.isNone:
return err("failed to get latest blockHeader")
let blk = res.get()
output.header = toBlockHeader(blk)
output.txs = toTransactions(blk.transactions)
return ok()
except ValueError as e:
return err(e.msg)
proc sendTransaction*(client: RpcClient, tx: common.Transaction): Result[void, string] =
try:
let encodedTx = rlp.encode(tx)
let res = waitFor client.eth_sendRawTransaction(hexDataStr(encodedTx))
let txHash = rlpHash(tx)
let getHash = Hash256(data: hexToByteArray[32](string res))
if txHash != getHash:
return err("sendTransaction: tx hash mismatch")
return ok()
except ValueError as e:
return err(e.msg)
proc balanceAt*(client: RpcClient, address: EthAddress): Result[UInt256, string] =
try:
let res = waitFor client.eth_getBalance(ethAddressStr(address), "latest")
return ok(UInt256.fromHex(res.string))
except ValueError as e:
return err(e.msg)
proc txReceipt*(client: RpcClient, txHash: Hash256): Result[eth_api.ReceiptObject, string] =
try:
let res = waitFor client.eth_getTransactionReceipt(txHash)
if res.isNone:
return err("failed to get receipt: " & txHash.data.toHex)
return ok(res.get)
except ValueError as e:
return err(e.msg)
proc storageAt*(client: RpcClient, address: EthAddress, slot: UInt256): Result[UInt256, string] =
try:
let res = waitFor client.eth_getStorageAt(ethAddressStr(address), encodeQuantity(slot), "latest")
return ok(UInt256.fromHex(res.string))
except ValueError as e:
return err(e.msg)
proc storageAt*(client: RpcClient, address: EthAddress, slot: UInt256, number: common.BlockNumber): Result[UInt256, string] =
try:
let tag = encodeQuantity(number)
let res = waitFor client.eth_getStorageAt(ethAddressStr(address), encodeQuantity(slot), tag.string)
return ok(UInt256.fromHex(res.string))
except ValueError as e:
return err(e.msg)

View File

@ -0,0 +1,19 @@
import
test_env,
engine_tests,
chronos,
unittest2
proc runTest(x: TestSpec, testStatusIMPL: var TestStatus) =
var t = setupELClient()
t.setRealTTD(x.ttd)
x.run(t, testStatusIMPL)
t.stopELClient()
proc main() =
suite "Engine Tests":
for x in engineTestList:
test x.name:
runTest(x, testStatusIMPL)
main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
{
"config": {
"chainId": 7,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x5de1ee4135274003348e80b788e5afa4b18b18d320a5622218d5c493fedf5689",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"clique": {
"epoch": 3000,
"period": 1
}
},
"genesis": {
"coinbase": "0x0000000000000000000000000000000000000000",
"difficulty": "0x30000",
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x2fefd8",
"nonce": "0x0000000000000000",
"timestamp": "0x1234",
"alloc": {
"cf49fda3be353c69b41ed96333cd24302da4556f": {
"balance": "0x123450000000000000000"
},
"0161e041aad467a890839d5b08b138c1e6373072": {
"balance": "0x123450000000000000000"
},
"87da6a8c6e9eff15d703fc2773e32f6af8dbe301": {
"balance": "0x123450000000000000000"
},
"b97de4b8c857e4f6bc354f226dc3249aaee49209": {
"balance": "0x123450000000000000000"
},
"c5065c9eeebe6df2c2284d046bfc906501846c51": {
"balance": "0x123450000000000000000"
},
"0000000000000000000000000000000000000314": {
"balance": "0x0",
"code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234",
"0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01"
}
},
"0000000000000000000000000000000000000315": {
"balance": "0x9999999999999999999999999999999",
"code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029"
},
"0000000000000000000000000000000000000316": {
"balance": "0x0",
"code": "0x444355"
},
"0000000000000000000000000000000000000317": {
"balance": "0x0",
"code": "0x600160003555"
}
}
}
}

View File

@ -0,0 +1,146 @@
import
std/typetraits,
test_env,
eth/rlp
type
ExecutableData* = object
parentHash* : Hash256
feeRecipient* : EthAddress
stateRoot* : Hash256
receiptsRoot* : Hash256
logsBloom* : BloomFilter
prevRandao* : Hash256
number* : uint64
gasLimit* : GasInt
gasUsed* : GasInt
timestamp* : EthTime
extraData* : Blob
baseFeePerGas*: UInt256
blockHash* : Hash256
transactions* : seq[Transaction]
CustomPayload* = object
parentHash* : Option[Hash256]
feeRecipient* : Option[EthAddress]
stateRoot* : Option[Hash256]
receiptsRoot* : Option[Hash256]
logsBloom* : Option[BloomFilter]
prevRandao* : Option[Hash256]
number* : Option[uint64]
gasLimit* : Option[GasInt]
gasUsed* : Option[GasInt]
timestamp* : Option[EthTime]
extraData* : Option[Blob]
baseFeePerGas*: Option[UInt256]
blockHash* : Option[Hash256]
transactions* : Option[seq[Transaction]]
proc customizePayload*(basePayload: ExecutableData, customData: CustomPayload): ExecutionPayloadV1 =
let txs = if customData.transactions.isSome:
customData.transactions.get
else:
basePayload.transactions
let txRoot = calcTxRoot(txs)
var customHeader = EthBlockHeader(
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)
)
# 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
# Return the new payload
result = ExecutionPayloadV1(
parentHash: Web3BlockHash customHeader.parentHash.data,
feeRecipient: Web3Address customHeader.coinbase,
stateRoot: Web3BlockHash customHeader.stateRoot.data,
receiptsRoot: Web3BlockHash customHeader.receiptRoot.data,
logsBloom: Web3Bloom customHeader.bloom,
prevRandao: Web3PrevRandao customHeader.mixDigest.data,
blockNumber: Web3Quantity customHeader.blockNumber.truncate(uint64),
gasLimit: Web3Quantity customHeader.gasLimit,
gasUsed: Web3Quantity customHeader.gasUsed,
timestamp: Web3Quantity toUnix(customHeader.timestamp),
extraData: Web3ExtraData customHeader.extraData,
baseFeePerGas: customHeader.baseFee,
blockHash: Web3BlockHash customHeader.blockHash.data
)
for tx in txs:
let txData = rlp.encode(tx)
result.transactions.add TypedTransaction(txData)
proc hash256*(h: Web3BlockHash): Hash256 =
Hash256(data: distinctBase h)
proc toExecutableData*(payload: ExecutionPayloadV1): ExecutableData =
result = ExecutableData(
parentHash : hash256(payload.parentHash),
feeRecipient : distinctBase payload.feeRecipient,
stateRoot : hash256(payload.stateRoot),
receiptsRoot : hash256(payload.receiptsRoot),
logsBloom : distinctBase payload.logsBloom,
prevRandao : hash256(payload.prevRandao),
number : uint64 payload.blockNumber,
gasLimit : GasInt payload.gasLimit,
gasUsed : GasInt payload.gasUsed,
timestamp : fromUnix(int64 payload.timestamp),
extraData : distinctBase payload.extraData,
baseFeePerGas : payload.baseFeePerGas,
blockHash : hash256(payload.blockHash)
)
for data in payload.transactions:
let tx = rlp.decode(distinctBase data, Transaction)
result.transactions.add tx

View File

@ -0,0 +1 @@
9c647b8b7c4e7c3490668fb6c11473619db80c93704c70893d3813af4090c39c

View File

@ -0,0 +1,151 @@
import
std/[os, options, json, times, math],
eth/[common, keys],
eth/trie/db,
eth/p2p as eth_p2p,
stew/[results, byteutils],
stint,
json_rpc/[rpcserver, rpcclient],
../../../nimbus/[
config,
genesis,
context,
constants,
transaction,
utils,
sealer,
p2p/chain,
db/db_chain,
rpc/p2p,
rpc/engine_api,
sync/protocol_ethxx,
utils/tx_pool
],
../../../tests/test_helpers,
"."/[clmock, engine_client]
import web3/engine_api_types
from web3/ethtypes as web3types import nil
export
common, engine_api_types, times,
options, results, constants, utils,
TypedTransaction, clmock, engine_client
type
EthBlockHeader* = common.BlockHeader
TestEnv* = ref object
conf: NimbusConf
ctx: EthContext
ethNode: EthereumNode
chainDB: BaseChainDB
chainRef: Chain
rpcServer: RpcSocketServer
sealingEngine: SealingEngineRef
rpcClient*: RpcSocketClient
gHeader*: EthBlockHeader
ttd*: DifficultyInt
clMock*: CLMocker
nonce: uint64
vaultKey: PrivateKey
Web3BlockHash* = web3types.BlockHash
Web3Address* = web3types.Address
Web3Bloom* = web3types.FixedBytes[256]
Web3Quantity* = web3types.Quantity
Web3PrevRandao* = web3types.FixedBytes[32]
Web3ExtraData* = web3types.DynamicBytes[0, 32]
const
baseFolder = "hive_integration" / "nodocker" / "engine"
genesisFile = baseFolder / "genesis.json"
sealerKey = baseFolder / "sealer.key"
# This is the account that sends vault funding transactions.
vaultAccountAddr* = hexToByteArray[20]("0xcf49fda3be353c69b41ed96333cd24302da4556f")
vaultKeyHex = "63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376"
proc setupELClient*(t: TestEnv) =
t.ctx = newEthContext()
let res = t.ctx.am.importPrivateKey(sealerKey)
if res.isErr:
echo res.error()
quit(QuitFailure)
t.ethNode = setupEthNode(t.conf, t.ctx, eth)
t.chainDB = newBaseChainDB(
newMemoryDb(),
t.conf.pruneMode == PruneMode.Full,
t.conf.networkId,
t.conf.networkParams
)
t.chainRef = newChain(t.chainDB)
initializeEmptyDb(t.chainDB)
let txPool = TxPoolRef.new(t.chainDB, t.conf.engineSigner)
t.rpcServer = newRpcSocketServer(["localhost:" & $t.conf.rpcPort])
t.sealingEngine = SealingEngineRef.new(
t.chainRef, t.ctx, t.conf.engineSigner,
txPool, EngineStopped
)
setupEthRpc(t.ethNode, t.ctx, t.chainDB, txPool, t.rpcServer)
setupEngineAPI(t.sealingEngine, t.rpcServer)
t.sealingEngine.start()
t.rpcServer.start()
t.rpcClient = newRpcSocketClient()
waitFor t.rpcClient.connect("localhost", t.conf.rpcPort)
t.gHeader = toGenesisHeader(t.conf.networkParams)
let kRes = PrivateKey.fromHex(vaultKeyHex)
if kRes.isErr:
echo kRes.error
quit(QuitFailure)
t.vaultKey = kRes.get
proc setupELClient*(): TestEnv =
result = TestEnv(
conf: makeConfig(@["--engine-signer:658bdf435d810c91414ec09147daa6db62406379", "--custom-network:" & genesisFile])
)
setupELClient(result)
proc stopELClient*(t: TestEnv) =
waitFor t.rpcClient.close()
waitFor t.sealingEngine.stop()
t.rpcServer.stop()
waitFor t.rpcServer.closeWait()
# TTD is the value specified in the TestSpec + Genesis.Difficulty
proc setRealTTD*(t: TestEnv, ttdValue: int64) =
let realTTD = t.gHeader.difficulty + ttdValue.u256
t.chainDB.config.terminalTotalDifficulty = some(realTTD)
t.ttd = realTTD
t.clmock = newCLMocker(t.rpcClient, realTTD)
func gwei(n: int): GasInt {.compileTime.} =
GasInt(n * (10 ^ 9))
proc makeNextTransaction*(t: TestEnv, recipient: EthAddress, amount: UInt256, payload: openArray[byte] = []): Transaction =
const
gasLimit = 75000.GasInt
gasPrice = 30.gwei
let chainId = t.conf.networkParams.config.chainId
let tx = Transaction(
txType : TxLegacy,
chainId : chainId,
nonce : AccountNonce(t.nonce),
gasPrice: gasPrice,
gasLimit: gasLimit,
to : some(recipient),
value : amount,
payload : @payload
)
inc t.nonce
signTransaction(tx, t.vaultKey, chainId, eip155 = true)

View File

@ -165,3 +165,15 @@ func eip1559TxNormalization*(tx: Transaction;
result.maxFee = tx.gasPrice result.maxFee = tx.gasPrice
if FkLondon <= fork: if FkLondon <= fork:
result.gasPrice = baseFee + min(result.maxPriorityFee, result.maxFee - baseFee) 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)

View File

@ -136,7 +136,7 @@ proc parseTransaction*(n: JsonNode): Transaction =
n.fromJson "r", tx.R n.fromJson "r", tx.R
n.fromJson "s", tx.S n.fromJson "s", tx.S
if n["type"].kind != JNull: if n.hasKey("type") and n["type"].kind != JNull:
n.fromJson "type", tx.txType n.fromJson "type", tx.txType
if tx.txType >= TxEip1559: if tx.txType >= TxEip1559: