engine-api-test: fix transactionReorg test case
This commit is contained in:
parent
b80eca0718
commit
71ac6b7de5
|
@ -114,7 +114,7 @@ proc pickNextPayloadProducer(cl: CLMocker): bool =
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc getNextPayloadID(cl: CLMocker): bool =
|
proc getNextPayloadID*(cl: CLMocker): bool =
|
||||||
# Generate a random value for the PrevRandao field
|
# Generate a random value for the PrevRandao field
|
||||||
var nextPrevRandao: Hash256
|
var nextPrevRandao: Hash256
|
||||||
doAssert nimcrypto.randomBytes(nextPrevRandao.data) == 32
|
doAssert nimcrypto.randomBytes(nextPrevRandao.data) == 32
|
||||||
|
@ -144,7 +144,7 @@ proc getNextPayloadID(cl: CLMocker): bool =
|
||||||
cl.nextPayloadID = s.payloadID.get()
|
cl.nextPayloadID = s.payloadID.get()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc getNextPayload(cl: CLMocker): bool =
|
proc getNextPayload*(cl: CLMocker): bool =
|
||||||
let res = cl.client.getPayloadV1(cl.nextPayloadID)
|
let res = cl.client.getPayloadV1(cl.nextPayloadID)
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
error "CLMocker: Could not getPayload",
|
error "CLMocker: Could not getPayload",
|
||||||
|
|
|
@ -91,6 +91,19 @@ template testNPEither(res, cond: untyped, validHash = none(Hash256)) =
|
||||||
testCond s.latestValidHash == validHash:
|
testCond s.latestValidHash == validHash:
|
||||||
error "Unexpected NewPayload latestValidHash", expect=validHash, get=s.latestValidHash
|
error "Unexpected NewPayload latestValidHash", expect=validHash, get=s.latestValidHash
|
||||||
|
|
||||||
|
template testLatestHeader(client: untyped, expectedHash: BlockHash) =
|
||||||
|
var lastHeader: EthBlockHeader
|
||||||
|
var hRes = client.latestHeader(lastHeader)
|
||||||
|
testCond hRes.isOk:
|
||||||
|
error "unable to get latest header", msg=hRes.error
|
||||||
|
|
||||||
|
let lastHash = BlockHash lastHeader.blockHash.data
|
||||||
|
# Latest block header available via Eth RPC should not have changed at this point
|
||||||
|
testCond lastHash == expectedHash:
|
||||||
|
error "latest block header incorrect",
|
||||||
|
expect = expectedHash.toHex,
|
||||||
|
get = lastHash.toHex
|
||||||
|
|
||||||
proc sendTx(t: TestEnv, recipient: EthAddress, val: UInt256, data: openArray[byte] = []): bool =
|
proc sendTx(t: TestEnv, recipient: EthAddress, val: UInt256, data: openArray[byte] = []): bool =
|
||||||
t.tx = t.makeNextTransaction(recipient, val, data)
|
t.tx = t.makeNextTransaction(recipient, val, data)
|
||||||
let rr = t.rpcClient.sendTransaction(t.tx)
|
let rr = t.rpcClient.sendTransaction(t.tx)
|
||||||
|
@ -412,12 +425,7 @@ template invalidPayloadAttributesGen(procname: untyped, syncingCond: bool) =
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Check that the forkchoice was applied, regardless of the error
|
# Check that the forkchoice was applied, regardless of the error
|
||||||
var header: EthBlockHeader
|
testLatestHeader(client, BlockHash blockHash.data)
|
||||||
let s = client.latestHeader(header)
|
|
||||||
if s.isErr:
|
|
||||||
return false
|
|
||||||
if header.blockHash != blockHash:
|
|
||||||
return false
|
|
||||||
return true
|
return true
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@ -644,14 +652,8 @@ proc invalidTransitionPayload(t: TestEnv): TestStatus =
|
||||||
)
|
)
|
||||||
testFCU(rr, invalid, some(Hash256()))
|
testFCU(rr, invalid, some(Hash256()))
|
||||||
|
|
||||||
var header: EthBlockHeader
|
testLatestHeader(client, clMock.latestExecutedPayload.blockHash)
|
||||||
let rz = client.latestHeader(header)
|
return true
|
||||||
if rz.isErr:
|
|
||||||
error "unable to get header", msg=rz.error
|
|
||||||
return false
|
|
||||||
|
|
||||||
let blockHash = BlockHash header.blockHash.data
|
|
||||||
blockHash == clMock.latestExecutedPayload.blockHash
|
|
||||||
))
|
))
|
||||||
|
|
||||||
testCond pbRes
|
testCond pbRes
|
||||||
|
@ -841,18 +843,7 @@ template blockStatusExecPayloadGen(procname: untyped, transitionBlock: bool) =
|
||||||
return true
|
return true
|
||||||
,
|
,
|
||||||
onNewPayloadBroadcast: proc(): bool =
|
onNewPayloadBroadcast: proc(): bool =
|
||||||
# TODO: Ideally, we would need to testCond that the newPayload returned VALID
|
testLatestHeader(client, clMock.latestForkchoice.headBlockHash)
|
||||||
var lastHeader: EthBlockHeader
|
|
||||||
var hRes = client.latestHeader(lastHeader)
|
|
||||||
if hRes.isErr:
|
|
||||||
error "unable to get latest header", msg=hRes.error
|
|
||||||
return false
|
|
||||||
|
|
||||||
let lastHash = BlockHash lastHeader.blockHash.data
|
|
||||||
# Latest block header available via Eth RPC should not have changed at this point
|
|
||||||
if lastHash!= clMock.latestForkchoice.headBlockHash:
|
|
||||||
error "latest block header incorrect after newPayload", hash=lastHash.toHex
|
|
||||||
return false
|
|
||||||
|
|
||||||
let nRes = client.blockNumber()
|
let nRes = client.blockNumber()
|
||||||
if nRes.isErr:
|
if nRes.isErr:
|
||||||
|
@ -906,16 +897,7 @@ template blockStatusHeadBlockGen(procname: untyped, transitionBlock: bool) =
|
||||||
,
|
,
|
||||||
# Run test after a forkchoice with new HeadBlockHash has been broadcasted
|
# Run test after a forkchoice with new HeadBlockHash has been broadcasted
|
||||||
onForkchoiceBroadcast: proc(): bool =
|
onForkchoiceBroadcast: proc(): bool =
|
||||||
var lastHeader: EthBlockHeader
|
testLatestHeader(client, clMock.latestForkchoice.headBlockHash)
|
||||||
var hRes = client.latestHeader(lastHeader)
|
|
||||||
if hRes.isErr:
|
|
||||||
error "unable to get latest header", msg=hRes.error
|
|
||||||
return false
|
|
||||||
|
|
||||||
let lastHash = BlockHash lastHeader.blockHash.data
|
|
||||||
if lastHash != clMock.latestForkchoice.headBlockHash:
|
|
||||||
error "latest block header doesn't match HeadBlock hash", hash=lastHash.toHex
|
|
||||||
return false
|
|
||||||
|
|
||||||
let rr = client.txReceipt(shadow.hash)
|
let rr = client.txReceipt(shadow.hash)
|
||||||
if rr.isErr:
|
if rr.isErr:
|
||||||
|
@ -1050,17 +1032,7 @@ proc blockStatusReorg(t: TestEnv): TestStatus =
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# testCond that we reorg to the previous block
|
# testCond that we reorg to the previous block
|
||||||
hRes = client.latestHeader(currHeader)
|
testLatestHeader(client, reorgForkchoice.headBlockHash)
|
||||||
if hRes.isErr:
|
|
||||||
error "unable to get latest header", msg=hRes.error
|
|
||||||
return false
|
|
||||||
|
|
||||||
currHash = BlockHash currHeader.blockHash.data
|
|
||||||
if currHash != reorgForkchoice.headBlockHash:
|
|
||||||
error "`latest` block hash doesn't match reorg hash",
|
|
||||||
expected=reorgForkchoice.headBlockHash.toHex,
|
|
||||||
get=currHash.toHex
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Send the HeadBlock again to leave everything back the way it was
|
# Send the HeadBlock again to leave everything back the way it was
|
||||||
res = client.forkchoiceUpdatedV1(clMock.latestForkchoice)
|
res = client.forkchoiceUpdatedV1(clMock.latestForkchoice)
|
||||||
|
@ -1249,11 +1221,7 @@ proc reorgBack(t: TestEnv): TestStatus =
|
||||||
testCond r2
|
testCond r2
|
||||||
|
|
||||||
# Verify that the client is pointing to the latest payload sent
|
# Verify that the client is pointing to the latest payload sent
|
||||||
var header: EthBlockHeader
|
testLatestHeader(client, clMock.latestPayloadBuilt.blockHash)
|
||||||
let r = client.latestHeader(header)
|
|
||||||
testCond r.isOk
|
|
||||||
let blockHash = hash256(clMock.latestPayloadBuilt.blockHash)
|
|
||||||
testCond blockHash == header.blockHash
|
|
||||||
|
|
||||||
# Test that performs a re-org back to the canonical chain after re-org to syncing/unavailable chain.
|
# Test that performs a re-org back to the canonical chain after re-org to syncing/unavailable chain.
|
||||||
type
|
type
|
||||||
|
@ -1328,6 +1296,11 @@ proc reorgBackFromSyncing(t: TestEnv): TestStatus =
|
||||||
|
|
||||||
testCond r2
|
testCond r2
|
||||||
|
|
||||||
|
type
|
||||||
|
TxReorgShadow = ref object
|
||||||
|
noTxnPayload: ExecutionPayloadV1
|
||||||
|
txHash: Hash256
|
||||||
|
|
||||||
proc transactionReorg(t: TestEnv): TestStatus =
|
proc transactionReorg(t: TestEnv): TestStatus =
|
||||||
result = TestStatus.OK
|
result = TestStatus.OK
|
||||||
|
|
||||||
|
@ -1343,89 +1316,87 @@ proc transactionReorg(t: TestEnv): TestStatus =
|
||||||
txCount = 5
|
txCount = 5
|
||||||
contractAddr = hexToByteArray[20]("0000000000000000000000000000000000000317")
|
contractAddr = hexToByteArray[20]("0000000000000000000000000000000000000317")
|
||||||
|
|
||||||
var
|
|
||||||
receipts: array[txCount, rpc_types.ReceiptObject]
|
|
||||||
txs: array[txCount, Transaction]
|
|
||||||
|
|
||||||
let
|
let
|
||||||
client = t.rpcClient
|
client = t.rpcClient
|
||||||
clMock = t.clMock
|
clMock = t.clMock
|
||||||
|
shadow = TxReorgShadow()
|
||||||
|
|
||||||
for i in 0..<txCount:
|
for i in 0..<txCount:
|
||||||
|
# Generate two payloads, one with the transaction and the other one without it
|
||||||
|
let pbres = clMock.produceSingleBlock(BlockProcessCallbacks(
|
||||||
|
onPayloadProducerSelected: proc(): bool =
|
||||||
|
# At this point we have not broadcast the transaction,
|
||||||
|
# therefore any payload we get should not contain any transactions
|
||||||
|
if not clMock.getNextPayloadID(): return false
|
||||||
|
if not clMock.getNextPayload(): return false
|
||||||
|
|
||||||
|
shadow.noTxnPayload = clMock.latestPayloadBuilt
|
||||||
|
if shadow.noTxnPayload.transactions.len != 0:
|
||||||
|
error "Empty payload contains transactions"
|
||||||
|
return false
|
||||||
|
|
||||||
|
# At this point we can broadcast the transaction and it will be included in the next payload
|
||||||
# Data is the key where a `1` will be stored
|
# Data is the key where a `1` will be stored
|
||||||
let data = i.u256
|
let data = i.u256
|
||||||
testCond t.sendTx(contractAddr, 0.u256, data.toBytesBE)
|
testCond t.sendTx(contractAddr, 0.u256, data.toBytesBE)
|
||||||
txs[i] = t.tx
|
shadow.txHash = rlpHash(t.tx)
|
||||||
|
|
||||||
# Produce the block containing the transaction
|
|
||||||
testCond clMock.produceSingleBlock(BlockProcessCallbacks())
|
|
||||||
|
|
||||||
# Get the receipt
|
# Get the receipt
|
||||||
let rr = client.txReceipt(rlpHash(t.tx))
|
let rr = client.txReceipt(shadow.txHash)
|
||||||
testCond rr.isOk:
|
if rr.isOk:
|
||||||
error "Unable to obtain transaction receipt", msg=rr.error
|
error "Receipt obtained before tx included in block"
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
,
|
||||||
|
onGetPayload: proc(): bool =
|
||||||
|
# Check that indeed the payload contains the transaction
|
||||||
|
if not txInPayload(clMock.latestPayloadBuilt, shadow.txHash):
|
||||||
|
error "Payload built does not contain the transaction"
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
,
|
||||||
|
onForkchoiceBroadcast: proc(): bool =
|
||||||
|
# Transaction is now in the head of the canonical chain, re-org and verify it's removed
|
||||||
|
var rr = client.txReceipt(shadow.txHash)
|
||||||
|
if rr.isErr:
|
||||||
|
error "Unable to obtain transaction receipt"
|
||||||
|
return false
|
||||||
|
|
||||||
receipts[i] = rr.get()
|
if shadow.noTxnPayload.parentHash != clMock.latestPayloadBuilt.parentHash:
|
||||||
|
error "Incorrect parent hash for payloads",
|
||||||
|
get = shadow.noTxnPayload.parentHash.toHex,
|
||||||
|
expect = clMock.latestPayloadBuilt.parentHash.toHex
|
||||||
|
return false
|
||||||
|
|
||||||
for i in 0..<txCount:
|
if shadow.noTxnPayload.blockHash == clMock.latestPayloadBuilt.blockHash:
|
||||||
# The sstore contract stores a `1` to key specified in data
|
error "Incorrect hash for payloads",
|
||||||
let storageKey = i.u256
|
get = shadow.noTxnPayload.blockHash.toHex,
|
||||||
|
expect = clMock.latestPayloadBuilt.blockHash.toHex
|
||||||
|
return false
|
||||||
|
|
||||||
var rr = client.storageAt(contractAddr, storageKey)
|
let rz = client.newPayloadV1(shadow.noTxnPayload)
|
||||||
testCond rr.isOk:
|
testNP(rz, valid, some(hash256(shadow.noTxnPayload.blockHash)))
|
||||||
error "Could not get storage", msg=rr.error
|
|
||||||
|
|
||||||
let valueWithTxApplied = rr.get()
|
let rx = client.forkchoiceUpdatedV1(ForkchoiceStateV1(
|
||||||
testCond valueWithTxApplied == 1.u256
|
headBlockHash: shadow.noTxnPayload.blockHash,
|
||||||
if valueWithTxApplied != 1.u256:
|
safeBlockHash: clMock.latestForkchoice.safeBlockHash,
|
||||||
error "Expected storage not set after transaction", valueWithTxApplied
|
finalizedBlockHash: clMock.latestForkchoice.finalizedBlockHash
|
||||||
return
|
))
|
||||||
|
testFCU(rx, valid)
|
||||||
|
|
||||||
# Get value at a block before the tx was included
|
testLatestHeader(client, shadow.noTxnPayload.blockHash)
|
||||||
let number = UInt256.fromHex(receipts[i].blockNumber.string).truncate(uint64)
|
|
||||||
var reorgBlock: EthBlockHeader
|
|
||||||
let blockRes = client.headerByNumber(number - 1, reorgBlock)
|
|
||||||
rr = client.storageAt(contractAddr, storageKey, reorgBlock.blockNumber)
|
|
||||||
testCond rr.isOk:
|
|
||||||
error "could not get storage", msg= rr.error
|
|
||||||
|
|
||||||
let valueWithoutTxApplied = rr.get()
|
let rk = client.txReceipt(shadow.txHash)
|
||||||
testCond valueWithoutTxApplied == 0.u256:
|
if rk.isOk:
|
||||||
error "Storage not unset before transaction!", valueWithoutTxApplied
|
error "Receipt was obtained when the tx had been re-org'd out"
|
||||||
|
return false
|
||||||
|
|
||||||
# Re-org back to a previous block where the tx is not included using forkchoiceUpdated
|
# Re-org back
|
||||||
let rHash = Web3BlockHash reorgBlock.blockHash.data
|
let ry = clMock.broadcastForkchoiceUpdated(clMock.latestForkchoice)
|
||||||
let reorgForkchoice = ForkchoiceStateV1(
|
ry.isOk
|
||||||
headBlockHash: rHash,
|
))
|
||||||
safeBlockHash: rHash,
|
|
||||||
finalizedBlockHash: rHash,
|
|
||||||
)
|
|
||||||
|
|
||||||
var res = client.forkchoiceUpdatedV1(reorgForkchoice)
|
testCond pbres
|
||||||
testCond res.isOk:
|
|
||||||
error "Could not send forkchoiceUpdatedV1", msg=res.error
|
|
||||||
|
|
||||||
var s = res.get()
|
|
||||||
testCond s.payloadStatus.status == PayloadExecutionStatus.valid:
|
|
||||||
error "Could not send forkchoiceUpdatedV1", status=s.payloadStatus.status
|
|
||||||
|
|
||||||
# testCond storage again using `latest`, should be unset
|
|
||||||
rr = client.storageAt( contractAddr, storageKey)
|
|
||||||
testCond rr.isOk:
|
|
||||||
error "could not get storage", msg= rr.error
|
|
||||||
|
|
||||||
let valueAfterReOrgBeforeTxApplied = rr.get()
|
|
||||||
testCond valueAfterReOrgBeforeTxApplied == 0.u256:
|
|
||||||
error "Storage not unset after re-org", valueAfterReOrgBeforeTxApplied
|
|
||||||
|
|
||||||
# Re-send latest forkchoice to test next transaction
|
|
||||||
res = client.forkchoiceUpdatedV1(clMock.latestForkchoice)
|
|
||||||
testCond res.isOk:
|
|
||||||
error "Could not send forkchoiceUpdatedV1", msg=res.error
|
|
||||||
|
|
||||||
s = res.get()
|
|
||||||
testCond s.payloadStatus.status == PayloadExecutionStatus.valid:
|
|
||||||
error "Could not send forkchoiceUpdatedV1", status=s.payloadStatus.status
|
|
||||||
|
|
||||||
proc testCondPrevRandaoValue(t: TestEnv, expectedPrevRandao: Hash256, blockNumber: uint64): bool =
|
proc testCondPrevRandaoValue(t: TestEnv, expectedPrevRandao: Hash256, blockNumber: uint64): bool =
|
||||||
let storageKey = blockNumber.u256
|
let storageKey = blockNumber.u256
|
||||||
|
|
|
@ -2,7 +2,7 @@ import
|
||||||
std/[typetraits, json, strutils],
|
std/[typetraits, json, strutils],
|
||||||
nimcrypto,
|
nimcrypto,
|
||||||
test_env,
|
test_env,
|
||||||
eth/[rlp, keys],
|
eth/[common, rlp, keys],
|
||||||
stew/byteutils,
|
stew/byteutils,
|
||||||
json_rpc/rpcclient,
|
json_rpc/rpcclient,
|
||||||
../../../nimbus/rpc/hexstrings,
|
../../../nimbus/rpc/hexstrings,
|
||||||
|
@ -332,3 +332,9 @@ proc generateInvalidPayload*(basePayload: ExecutionPayloadV1,
|
||||||
payloadField: InvalidPayloadField,
|
payloadField: InvalidPayloadField,
|
||||||
vaultKey = default(PrivateKey)): ExecutionPayloadV1 =
|
vaultKey = default(PrivateKey)): ExecutionPayloadV1 =
|
||||||
generateInvalidPayload(basePayload.toExecutableData, payloadField, vaultKey)
|
generateInvalidPayload(basePayload.toExecutableData, payloadField, vaultKey)
|
||||||
|
|
||||||
|
proc txInPayload*(payload: ExecutionPayloadV1, txHash: Hash256): bool =
|
||||||
|
for txBytes in payload.transactions:
|
||||||
|
let currTx = rlp.decode(Blob txBytes, Transaction)
|
||||||
|
if rlpHash(currTx) == txHash:
|
||||||
|
return true
|
||||||
|
|
Loading…
Reference in New Issue