add invalidMissingAncestorReOrg test case
This commit is contained in:
parent
4a50b00c37
commit
7f0bc71b65
|
@ -34,8 +34,8 @@ type
|
|||
client : RpcClient
|
||||
ttd : DifficultyInt
|
||||
|
||||
slotsToSafe : int
|
||||
slotsToFinalized : int
|
||||
slotsToSafe* : int
|
||||
slotsToFinalized* : int
|
||||
headHashHistory : seq[BlockHash]
|
||||
|
||||
BlockProcessCallbacks* = object
|
||||
|
|
|
@ -167,7 +167,7 @@ proc namedHeader*(client: RpcClient, name: string, output: var common.BlockHeade
|
|||
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)
|
||||
|
@ -196,9 +196,14 @@ proc txReceipt*(client: RpcClient, txHash: Hash256): Result[eth_api.ReceiptObjec
|
|||
except ValueError as e:
|
||||
return err(e.msg)
|
||||
|
||||
proc toDataStr(slot: UInt256): HexDataStr =
|
||||
let hex = slot.toHex
|
||||
let prefix = if hex.len mod 2 == 0: "0x" else: "0x0"
|
||||
HexDataStr(prefix & hex)
|
||||
|
||||
proc storageAt*(client: RpcClient, address: EthAddress, slot: UInt256): Result[UInt256, string] =
|
||||
try:
|
||||
let res = waitFor client.eth_getStorageAt(ethAddressStr(address), encodeQuantity(slot), "latest")
|
||||
let res = waitFor client.eth_getStorageAt(ethAddressStr(address), toDataStr(slot), "latest")
|
||||
return ok(UInt256.fromHex(res.string))
|
||||
except ValueError as e:
|
||||
return err(e.msg)
|
||||
|
@ -206,7 +211,7 @@ proc storageAt*(client: RpcClient, address: EthAddress, slot: UInt256): Result[U
|
|||
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)
|
||||
let res = waitFor client.eth_getStorageAt(ethAddressStr(address), toDataStr(slot), tag.string)
|
||||
return ok(UInt256.fromHex(res.string))
|
||||
except ValueError as e:
|
||||
return err(e.msg)
|
||||
|
|
|
@ -11,6 +11,10 @@ proc main() =
|
|||
for x in engineTestList:
|
||||
var t = setupELClient(x.chainFile)
|
||||
t.setRealTTD(x.ttd)
|
||||
if x.slotsToFinalized != 0:
|
||||
t.slotsToFinalized(x.slotsToFinalized)
|
||||
if x.slotsToSafe != 0:
|
||||
t.slotsToSafe(x.slotsToSafe)
|
||||
let status = x.run(t)
|
||||
t.stopELClient()
|
||||
stat.inc(x.name, status)
|
||||
|
|
|
@ -17,6 +17,8 @@ type
|
|||
run*: proc(t: TestEnv): TestStatus
|
||||
ttd*: int64
|
||||
chainFile*: string
|
||||
slotsToFinalized*: int
|
||||
slotsToSafe*: int
|
||||
|
||||
const
|
||||
prevRandaoContractAddr = hexToByteArray[20]("0000000000000000000000000000000000000316")
|
||||
|
@ -871,6 +873,130 @@ template blockStatusExecPayloadGen(procname: untyped, transitionBlock: bool) =
|
|||
blockStatusExecPayloadGen(blockStatusExecPayload1, false)
|
||||
blockStatusExecPayloadGen(blockStatusExecPayload2, true)
|
||||
|
||||
type
|
||||
MissingAncestorShadow = ref object
|
||||
cA: ExecutionPayloadV1
|
||||
n: int
|
||||
altChainPayloads: seq[ExecutionPayloadV1]
|
||||
|
||||
# Attempt to re-org to a chain which at some point contains an unknown payload which is also invalid.
|
||||
# Then reveal the invalid payload and expect that the client rejects it and rejects forkchoice updated calls to this chain.
|
||||
# The invalid_index parameter determines how many payloads apart is the common ancestor from the block that invalidates the chain,
|
||||
# with a value of 1 meaning that the immediate payload after the common ancestor will be invalid.
|
||||
template invalidMissingAncestorReOrgGen(procName: untyped,
|
||||
invalid_index: int, payloadField: InvalidPayloadField, p2psync: bool, emptyTxs: bool) =
|
||||
|
||||
proc procName(t: TestEnv): TestStatus =
|
||||
result = TestStatus.OK
|
||||
|
||||
# Wait until TTD is reached by this client
|
||||
let ok = waitFor t.clMock.waitForTTD()
|
||||
testCond ok
|
||||
|
||||
let clMock = t.clMock
|
||||
let client = t.rpcClient
|
||||
|
||||
# Produce blocks before starting the test
|
||||
testCond clMock.produceBlocks(5, BlockProcessCallbacks())
|
||||
|
||||
let shadow = MissingAncestorShadow(
|
||||
# Save the common ancestor
|
||||
cA: clMock.latestPayloadBuilt,
|
||||
|
||||
# Amount of blocks to deviate starting from the common ancestor
|
||||
n: 10,
|
||||
|
||||
# Slice to save the alternate B chain
|
||||
altChainPayloads: @[]
|
||||
)
|
||||
|
||||
# Append the common ancestor
|
||||
shadow.altChainPayloads.add shadow.cA
|
||||
|
||||
# Produce blocks but at the same time create an alternate chain which contains an invalid payload at some point (INV_P)
|
||||
# CommonAncestor◄─▲── P1 ◄─ P2 ◄─ P3 ◄─ ... ◄─ Pn
|
||||
# │
|
||||
# └── P1' ◄─ P2' ◄─ ... ◄─ INV_P ◄─ ... ◄─ Pn'
|
||||
var pbRes = clMock.produceBlocks(shadow.n, BlockProcessCallbacks(
|
||||
onPayloadProducerSelected: proc(): bool =
|
||||
# Function to send at least one transaction each block produced.
|
||||
# Empty Txs Payload with invalid stateRoot discovered an issue in geth sync, hence this is customizable.
|
||||
when not emptyTxs:
|
||||
# Send the transaction to the prevRandaoContractAddr
|
||||
t.sendTx(1.u256)
|
||||
return true
|
||||
,
|
||||
onGetPayload: proc(): bool =
|
||||
# Insert extraData to ensure we deviate from the main payload, which contains empty extradata
|
||||
var alternatePayload = customizePayload(clMock.latestPayloadBuilt, CustomPayload(
|
||||
parentHash: some(shadow.altChainPayloads[^1].blockHash.hash256),
|
||||
extraData: some(@[1.byte]),
|
||||
))
|
||||
|
||||
if shadow.altChainPayloads.len == invalid_index:
|
||||
alternatePayload = generateInvalidPayload(alternatePayload, payloadField)
|
||||
|
||||
shadow.altChainPayloads.add alternatePayload
|
||||
return true
|
||||
))
|
||||
testCond pbRes
|
||||
|
||||
pbRes = clMock.produceSingleBlock(BlockProcessCallbacks(
|
||||
# Note: We perform the test in the middle of payload creation by the CL Mock, in order to be able to
|
||||
# re-org back into this chain and use the new payload without issues.
|
||||
onGetPayload: proc(): bool =
|
||||
# Now let's send the alternate chain to the client using newPayload/sync
|
||||
for i in 1..shadow.n:
|
||||
# Send the payload
|
||||
var payloadValidStr = "VALID"
|
||||
if i == invalid_index:
|
||||
payloadValidStr = "INVALID"
|
||||
elif i > invalid_index:
|
||||
payloadValidStr = "VALID with INVALID ancestor"
|
||||
|
||||
info "Invalid chain payload",
|
||||
i,
|
||||
payloadValidStr,
|
||||
hash = shadow.altChainPayloads[i].blockHash.toHex
|
||||
|
||||
let rr = client.newPayloadV1(shadow.altChainPayloads[i])
|
||||
testCond rr.isOk
|
||||
|
||||
let rs = client.forkchoiceUpdatedV1(ForkchoiceStateV1(
|
||||
headBlockHash: shadow.altChainPayloads[i].blockHash,
|
||||
safeBlockHash: shadow.altChainPayloads[i].blockHash
|
||||
))
|
||||
|
||||
if i == invalid_index:
|
||||
# If this is the first payload after the common ancestor, and this is the payload we invalidated,
|
||||
# then we have all the information to determine that this payload is invalid.
|
||||
testNP(rr, invalid, some(shadow.altChainPayloads[i-1].blockHash.hash256))
|
||||
elif i > invalid_index:
|
||||
# We have already sent the invalid payload, but the client could've discarded it.
|
||||
# In reality the CL will not get to this point because it will have already received the `INVALID`
|
||||
# response from the previous payload.
|
||||
let cond = {PayloadExecutionStatus.accepted, PayloadExecutionStatus.syncing, PayloadExecutionStatus.invalid}
|
||||
testNPEither(rr, cond, some(Hash256()))
|
||||
else:
|
||||
# This is one of the payloads before the invalid one, therefore is valid.
|
||||
testNP(rr, valid)
|
||||
testFCU(rs, valid, some(shadow.altChainPayloads[i].blockHash.hash256))
|
||||
|
||||
|
||||
# Resend the latest correct fcU
|
||||
let rx = client.forkchoiceUpdatedV1(clMock.latestForkchoice)
|
||||
testCond rx.isOk
|
||||
|
||||
# After this point, the CL Mock will send the next payload of the canonical chain
|
||||
return true
|
||||
))
|
||||
|
||||
testCond pbRes
|
||||
|
||||
invalidMissingAncestorReOrgGen(invalidMissingAncestor1, 1, InvalidStateRoot, false, true)
|
||||
invalidMissingAncestorReOrgGen(invalidMissingAncestor2, 9, InvalidStateRoot, false, true)
|
||||
invalidMissingAncestorReOrgGen(invalidMissingAncestor3, 10, InvalidStateRoot, false, true)
|
||||
|
||||
template blockStatusHeadBlockGen(procname: untyped, transitionBlock: bool) =
|
||||
proc procName(t: TestEnv): TestStatus =
|
||||
result = TestStatus.OK
|
||||
|
@ -1819,6 +1945,23 @@ const engineTestList* = [
|
|||
run: invalidPayload15,
|
||||
),
|
||||
|
||||
# Invalid Ancestor Re-Org Tests (Reveal via newPayload)
|
||||
TestSpec(
|
||||
name: "Invalid Ancestor Chain Re-Org, Invalid StateRoot, Invalid P1', Reveal using newPayload",
|
||||
slotsToFinalized: 20,
|
||||
run: invalidMissingAncestor1,
|
||||
),
|
||||
TestSpec(
|
||||
name: "Invalid Ancestor Chain Re-Org, Invalid StateRoot, Invalid P9', Reveal using newPayload",
|
||||
slotsToFinalized: 20,
|
||||
run: invalidMissingAncestor2,
|
||||
),
|
||||
TestSpec(
|
||||
name: "Invalid Ancestor Chain Re-Org, Invalid StateRoot, Invalid P10', Reveal using newPayload",
|
||||
slotsToFinalized: 20,
|
||||
run: invalidMissingAncestor3,
|
||||
),
|
||||
|
||||
# Eth RPC Status on ForkchoiceUpdated Events
|
||||
TestSpec( # TODO: fix/debug
|
||||
name: "Latest Block after NewPayload",
|
||||
|
|
|
@ -180,6 +180,9 @@ proc toExecutableData*(payload: ExecutionPayloadV1): ExecutableData =
|
|||
let tx = rlp.decode(distinctBase data, Transaction)
|
||||
result.transactions.add tx
|
||||
|
||||
proc customizePayload*(basePayload: ExecutionPayloadV1, customData: CustomPayload): ExecutionPayloadV1 =
|
||||
customizePayload(basePayload.toExecutableData, customData)
|
||||
|
||||
proc debugPrevRandaoTransaction*(client: RpcClient, tx: Transaction, expectedPrevRandao: Hash256): Result[void, string] =
|
||||
try:
|
||||
let hash = tx.rlpHash
|
||||
|
|
|
@ -142,6 +142,12 @@ proc setRealTTD*(t: TestEnv, ttdValue: int64) =
|
|||
t.ttd = realTTD
|
||||
t.clmock = newCLMocker(t.rpcClient, realTTD)
|
||||
|
||||
proc slotsToSafe*(t: TestEnv, x: int) =
|
||||
t.clMock.slotsToSafe = x
|
||||
|
||||
proc slotsToFinalized*(t: TestEnv, x: int) =
|
||||
t.clMock.slotsToFinalized = x
|
||||
|
||||
func gwei(n: int): GasInt {.compileTime.} =
|
||||
GasInt(n * (10 ^ 9))
|
||||
|
||||
|
|
Loading…
Reference in New Issue