Add BeaconSync reorg tests (#1782)

* Add BeaconSync reorg tests

* Fix redefinition error in tx_sender.nim
This commit is contained in:
andri lim 2023-09-30 19:20:29 +07:00 committed by GitHub
parent 2f2c5127ea
commit 501d8a369a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 733 additions and 566 deletions

View File

@ -75,11 +75,6 @@ type
onSafeBlockChange * : proc(): bool {.gcsafe.}
onFinalizedBlockChange* : proc(): bool {.gcsafe.}
GetPayloadResponse = object
executionPayload: ExecutionPayload
blockValue: Option[UInt256]
blobsBundle: Option[BlobsBundleV1]
func latestPayloadNumber*(h: Table[uint64, ExecutionPayload]): uint64 =
result = 0'u64
for n, _ in h:
@ -99,17 +94,21 @@ func latestWithdrawalsIndex*(h: Table[uint64, ExecutionPayload]): uint64 =
func client(cl: CLMocker): RpcClient =
cl.clients.first.client
proc init(cl: CLMocker, clients: ClientPool, com: CommonRef) =
cl.clients = clients
proc init(cl: CLMocker, eng: EngineEnv, com: CommonRef) =
cl.clients = ClientPool()
cl.clients.add eng
cl.com = com
cl.slotsToSafe = 1
cl.slotsToFinalized = 2
cl.payloadProductionClientDelay = 1
cl.headerHistory[0] = com.genesisHeader()
proc newClMocker*(clients: ClientPool, com: CommonRef): CLMocker =
proc newClMocker*(eng: EngineEnv, com: CommonRef): CLMocker =
new result
result.init(clients, com)
result.init(eng, com)
proc addEngine*(cl: CLMocker, eng: EngineEnv) =
cl.clients.add eng
proc waitForTTD*(cl: CLMocker): Future[bool] {.async.} =
let ttd = cl.com.ttd()
@ -193,32 +192,6 @@ func isCancun(cl: CLMocker, timestamp: Quantity): bool =
let ts = fromUnix(timestamp.int64)
cl.com.isCancunOrLater(ts)
func V1(attr: Option[PayloadAttributes]): Option[PayloadAttributesV1] =
if attr.isNone:
return none(PayloadAttributesV1)
some(attr.get.V1)
when false:
func V2(attr: Option[PayloadAttributes]): Option[PayloadAttributesV2] =
if attr.isNone:
return none(PayloadAttributesV2)
some(attr.get.V2)
func V3(attr: Option[PayloadAttributes]): Option[PayloadAttributesV3] =
if attr.isNone:
return none(PayloadAttributesV3)
some(attr.get.V3)
proc fcu(cl: CLMocker, version: Version,
update: ForkchoiceStateV1,
attr: Option[PayloadAttributes]):
Result[ForkchoiceUpdatedResponse, string] =
let client = cl.nextBlockProducer.client
case version
of Version.V1: client.forkchoiceUpdatedV1(update, attr.V1)
of Version.V2: client.forkchoiceUpdatedV2(update, attr)
of Version.V3: client.forkchoiceUpdatedV3(update, attr)
# Picks the next payload producer from the set of clients registered
proc pickNextPayloadProducer(cl: CLMocker): bool =
doAssert cl.clients.len != 0
@ -274,7 +247,8 @@ proc requestNextPayload(cl: CLMocker): bool =
cl.prevRandaoHistory[number] = nextPrevRandao
let version = cl.latestPayloadAttributes.version
let res = cl.fcu(version, cl.latestForkchoice, some(cl.latestPayloadAttributes))
let client = cl.nextBlockProducer.client
let res = client.forkchoiceUpdated(version, cl.latestForkchoice, some(cl.latestPayloadAttributes))
if res.isErr:
error "CLMocker: Could not send forkchoiceUpdated", version=version, msg=res.error
return false
@ -299,32 +273,11 @@ proc getPayload(cl: CLMocker, payloadId: PayloadID): Result[GetPayloadResponse,
let ts = cl.latestPayloadAttributes.timestamp
let client = cl.nextBlockProducer.client
if cl.isCancun(ts):
let res = client.getPayloadV3(payloadId)
if res.isErr:
return err(res.error)
let x = res.get
return ok(GetPayloadResponse(
executionPayload: executionPayload(x.executionPayload),
blockValue: some(x.blockValue),
blobsBundle: some(x.blobsBundle)
))
if cl.isShanghai(ts):
let res = client.getPayloadV2(payloadId)
if res.isErr:
return err(res.error)
let x = res.get
return ok(GetPayloadResponse(
executionPayload: executionPayload(x.executionPayload),
blockValue: some(x.blockValue)
))
let res = client.getPayloadV1(payloadId)
if res.isErr:
return err(res.error)
return ok(GetPayloadResponse(
executionPayload: executionPayload(res.get),
))
client.getPayload(payloadId, Version.V3)
elif cl.isShanghai(ts):
client.getPayload(payloadId, Version.V2)
else:
client.getPayload(payloadId, Version.V1)
proc getNextPayload(cl: CLMocker): bool =
let res = cl.getPayload(cl.nextPayloadID)
@ -451,7 +404,8 @@ proc broadcastNextNewPayload(cl: CLMocker): bool =
proc broadcastForkchoiceUpdated(cl: CLMocker,
update: ForkchoiceStateV1): Result[ForkchoiceUpdatedResponse, string] =
let version = cl.latestExecutedPayload.version
cl.fcu(version, update, none(PayloadAttributes))
let client = cl.nextBlockProducer.client
client.forkchoiceUpdated(version, update, none(PayloadAttributes))
proc broadcastLatestForkchoice(cl: CLMocker): bool =
let res = cl.broadcastForkchoiceUpdated(cl.latestForkchoice)
@ -480,7 +434,6 @@ proc broadcastLatestForkchoice(cl: CLMocker): bool =
return true
proc produceSingleBlock*(cl: CLMocker, cb: BlockProcessCallbacks): bool {.gcsafe.} =
doAssert(cl.ttdReached)

View File

@ -19,9 +19,6 @@ type
slotsToFinalized*: int
slotsToSafe*: int
const
prevRandaoContractAddr = hexToByteArray[20]("0000000000000000000000000000000000000316")
template testNP(res, cond: untyped, validHash = none(common.Hash256)) =
testCond res.isOk
let s = res.get()

View File

@ -11,7 +11,9 @@ import
import web3/engine_api as web3_engine_api
export execution_types
export
execution_types,
rpcclient
type
Hash256 = eth_types.Hash256
@ -43,20 +45,6 @@ proc forkchoiceUpdatedV1*(client: RpcClient,
wrapTrySimpleRes:
client.engine_forkchoiceUpdatedV1(update, payloadAttributes)
#proc forkchoiceUpdatedV2*(client: RpcClient,
# update: ForkchoiceStateV1,
# payloadAttributes = none(PayloadAttributesV2)):
# Result[ForkchoiceUpdatedResponse, string] =
# wrapTrySimpleRes:
# client.engine_forkchoiceUpdatedV2(update, payloadAttributes)
#proc forkchoiceUpdatedV3*(client: RpcClient,
# update: ForkchoiceStateV1,
# payloadAttributes = none(PayloadAttributesV3)):
# Result[ForkchoiceUpdatedResponse, string] =
# wrapTrySimpleRes:
# client.engine_forkchoiceUpdatedV3(update, payloadAttributes)
proc forkchoiceUpdatedV2*(client: RpcClient,
update: ForkchoiceStateV1,
payloadAttributes = none(PayloadAttributes)):
@ -83,6 +71,50 @@ proc getPayloadV3*(client: RpcClient, payloadId: PayloadID): Result[GetPayloadV3
wrapTrySimpleRes:
client.engine_getPayloadV3(payloadId)
proc getPayload*(client: RpcClient,
payloadId: PayloadID,
version: Version): Result[GetPayloadResponse, string] =
if version == Version.V3:
let x = client.getPayloadV3(payloadId).valueOr:
return err(error)
ok(GetPayloadResponse(
executionPayload: executionPayload(x.executionPayload),
blockValue: some(x.blockValue),
blobsBundle: some(x.blobsBundle)
))
elif version == Version.V2:
let x = client.getPayloadV2(payloadId).valueOr:
return err(error)
ok(GetPayloadResponse(
executionPayload: executionPayload(x.executionPayload),
blockValue: some(x.blockValue)
))
else:
let x = client.getPayloadV1(payloadId).valueOr:
return err(error)
ok(GetPayloadResponse(
executionPayload: executionPayload(x),
))
proc forkchoiceUpdated*(client: RpcClient,
update: ForkchoiceStateV1,
attr: PayloadAttributes):
Result[ForkchoiceUpdatedResponse, string] =
case attr.version
of Version.V1: client.forkchoiceUpdatedV1(update, some attr.V1)
of Version.V2: client.forkchoiceUpdatedV2(update, some attr)
of Version.V3: client.forkchoiceUpdatedV3(update, some attr)
proc forkchoiceUpdated*(client: RpcClient,
version: Version,
update: ForkchoiceStateV1,
attr = none(PayloadAttributes)):
Result[ForkchoiceUpdatedResponse, string] =
case version
of Version.V1: client.forkchoiceUpdatedV1(update, attr.V1)
of Version.V2: client.forkchoiceUpdatedV2(update, attr)
of Version.V3: client.forkchoiceUpdatedV3(update, attr)
proc newPayloadV1*(client: RpcClient,
payload: ExecutionPayloadV1):
Result[PayloadStatusV1, string] =
@ -110,6 +142,15 @@ proc newPayloadV3*(client: RpcClient,
wrapTrySimpleRes:
client.engine_newPayloadV3(payload, versionedHashes, parentBeaconBlockRoot)
proc newPayload*(client: RpcClient,
payload: ExecutionPayload,
version: Version):
Result[PayloadStatusV1, string] =
if version == Version.V1:
client.newPayloadV1(payload.V1)
else:
client.newPayloadV2(payload.V2)
proc exchangeCapabilities*(client: RpcClient,
methods: seq[string]):
Result[seq[string], string] =

View File

@ -1,14 +1,13 @@
import
std/[os, math],
std/os,
eth/keys,
eth/p2p as eth_p2p,
chronos,
json_rpc/[rpcserver, rpcclient],
stew/[results, byteutils],
stew/[results],
../../../nimbus/[
config,
constants,
transaction,
core/sealer,
core/chain,
core/tx_pool,
@ -20,25 +19,12 @@ import
beacon/beacon_engine,
common
],
../../../tests/test_helpers,
./engine_client
../../../tests/test_helpers
export
results
type
BaseTx* = object of RootObj
recipient*: Option[EthAddress]
gasLimit* : GasInt
amount* : UInt256
payload* : seq[byte]
txType* : Option[TxType]
BigInitcodeTx* = object of BaseTx
initcodeLength*: int
padByte* : uint8
initcode* : seq[byte]
EngineEnv* = ref object
conf : NimbusConf
com : CommonRef
@ -46,8 +32,6 @@ type
server : RpcHttpServer
sealer : SealingEngineRef
ttd : DifficultyInt
tx : Transaction
nonce : uint64
client : RpcHttpClient
sync : BeaconSyncRef
@ -56,12 +40,8 @@ const
genesisFile = baseFolder & "/init/genesis.json"
sealerKey = baseFolder & "/init/sealer.key"
chainFolder = baseFolder & "/chains"
# This is the account that sends vault funding transactions.
vaultAddr* = hexToByteArray[20]("0xcf49fda3be353c69b41ed96333cd24302da4556f")
jwtSecret = "0x7365637265747365637265747365637265747365637265747365637265747365"
proc makeCom*(conf: NimbusConf): CommonRef =
CommonRef.new(
newCoreDbRef LegacyDbMemory,
@ -189,98 +169,3 @@ func node*(env: EngineEnv): ENode =
proc connect*(env: EngineEnv, node: ENode) =
waitFor env.node.connectToNode(node)
func gwei(n: int64): GasInt {.compileTime.} =
GasInt(n * (10 ^ 9))
proc getTxType(tc: BaseTx, nonce: uint64): TxType =
if tc.txType.isNone:
if nonce mod 2 == 0:
TxLegacy
else:
TxEIP1559
else:
tc.txType.get
proc makeTx*(env: EngineEnv, vaultKey: PrivateKey, tc: BaseTx, nonce: AccountNonce): Transaction =
const
gasPrice = 30.gwei
gasTipPrice = 1.gwei
gasFeeCap = gasPrice
gasTipCap = gasTipPrice
let chainId = env.conf.networkParams.config.chainId
let txType = tc.getTxType(nonce)
# Build the transaction depending on the specified type
let tx = if txType == TxLegacy:
Transaction(
txType : TxLegacy,
nonce : nonce,
to : tc.recipient,
value : tc.amount,
gasLimit: tc.gasLimit,
gasPrice: gasPrice,
payload : tc.payload
)
else:
Transaction(
txType : TxEIP1559,
nonce : nonce,
gasLimit: tc.gasLimit,
maxFee : gasFeeCap,
maxPriorityFee: gasTipCap,
to : tc.recipient,
value : tc.amount,
payload : tc.payload,
chainId : chainId
)
signTransaction(tx, vaultKey, chainId, eip155 = true)
proc makeTx*(env: EngineEnv, vaultKey: PrivateKey, tc: var BigInitcodeTx, nonce: AccountNonce): Transaction =
if tc.payload.len == 0:
# Prepare initcode payload
if tc.initcode.len != 0:
doAssert(tc.initcode.len <= tc.initcodeLength, "invalid initcode (too big)")
tc.payload = tc.initcode
while tc.payload.len < tc.initcodeLength:
tc.payload.add tc.padByte
doAssert(tc.recipient.isNone, "invalid configuration for big contract tx creator")
env.makeTx(vaultKey, tc.BaseTx, nonce)
proc sendNextTx*(env: EngineEnv, vaultKey: PrivateKey, tc: BaseTx): bool =
env.tx = env.makeTx(vaultKey, tc, env.nonce)
inc env.nonce
let rr = env.client.sendTransaction(env.tx)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
return true
proc sendTx*(env: EngineEnv, vaultKey: PrivateKey, tc: BaseTx, nonce: AccountNonce): bool =
env.tx = env.makeTx(vaultKey, tc, nonce)
let rr = env.client.sendTransaction(env.tx)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
return true
proc sendTx*(env: EngineEnv, vaultKey: PrivateKey, tc: BigInitcodeTx, nonce: AccountNonce): bool =
env.tx = env.makeTx(vaultKey, tc, nonce)
let rr = env.client.sendTransaction(env.tx)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
return true
proc sendTx*(env: EngineEnv, tx: Transaction): bool =
env.tx = tx
let rr = env.client.sendTransaction(env.tx)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
return true

View File

@ -7,13 +7,15 @@ import
./clmock,
./engine_client,
./client_pool,
./engine_env
./engine_env,
./tx_sender
export
clmock,
engine_client,
client_pool,
engine_env
engine_env,
tx_sender
type
TestEnv* = ref object
@ -23,26 +25,18 @@ type
port : int
rpcPort : int
clients : ClientPool
sender : TxSender
clMock* : CLMocker
vaultKey : PrivateKey
const
vaultKeyHex = "63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376"
proc makeEnv(conf: NimbusConf): TestEnv =
let env = TestEnv(
TestEnv(
conf : conf,
port : 30303,
rpcPort: 8545,
clients: ClientPool(),
sender : TxSender.new(conf.networkParams),
)
env.vaultKey = PrivateKey.fromHex(vaultKeyHex).valueOr:
echo error
quit(QuitFailure)
env
proc addEngine(env: TestEnv, conf: var NimbusConf): EngineEnv =
conf.tcpPort = Port env.port
conf.udpPort = Port env.port
@ -85,47 +79,58 @@ func engine*(env: TestEnv): EngineEnv =
env.clients.first
proc setupCLMock*(env: TestEnv) =
env.clmock = newCLMocker(env.clients, env.engine.com)
env.clmock = newCLMocker(env.engine, env.engine.com)
proc addEngine*(env: TestEnv): EngineEnv =
proc addEngine*(env: TestEnv, addToCL: bool = true): EngineEnv =
doAssert(env.clMock.isNil.not)
var conf = env.conf # clone the conf
let eng = env.addEngine(conf)
eng.connect(env.engine.node)
if addToCL:
env.clMock.addEngine(eng)
eng
proc makeTx*(env: TestEnv, eng: EngineEnv, tc: BaseTx, nonce: AccountNonce): Transaction =
eng.makeTx(env.vaultKey, tc, nonce)
proc makeTx*(env: TestEnv, tc: BaseTx, nonce: AccountNonce): Transaction =
env.sender.makeTx(tc, nonce)
proc makeTx*(env: TestEnv, eng: EngineEnv, tc: var BigInitcodeTx, nonce: AccountNonce): Transaction =
eng.makeTx(env.vaultKey, tc, nonce)
proc makeTx*(env: TestEnv, tc: BigInitcodeTx, nonce: AccountNonce): Transaction =
env.sender.makeTx(tc, nonce)
proc makeTxs*(env: TestEnv, tc: BaseTx, num: int): seq[Transaction] =
result = newSeqOfCap[Transaction](num)
for _ in 0..<num:
result.add env.sender.makeNextTx(tc)
proc sendNextTx*(env: TestEnv, eng: EngineEnv, tc: BaseTx): bool =
eng.sendNextTx(env.vaultKey, tc)
env.sender.sendNextTx(eng.client, tc)
proc sendTx*(env: TestEnv, eng: EngineEnv, tc: BaseTx, nonce: AccountNonce): bool =
eng.sendTx(env.vaultKey, tc, nonce)
env.sender.sendTx(eng.client, tc, nonce)
proc sendTx*(env: TestEnv, eng: EngineEnv, tc: BigInitcodeTx, nonce: AccountNonce): bool =
eng.sendTx(env.vaultKey, tc, nonce)
env.sender.sendTx(eng.client, tc, nonce)
proc makeTx*(env: TestEnv, tc: BaseTx, nonce: AccountNonce): Transaction =
env.engine.makeTx(env.vaultKey, tc, nonce)
proc makeTx*(env: TestEnv, tc: var BigInitcodeTx, nonce: AccountNonce): Transaction =
env.engine.makeTx(env.vaultKey, tc, nonce)
proc sendTxs*(env: TestEnv, eng: EngineEnv, txs: openArray[Transaction]): bool =
for tx in txs:
if not sendTx(eng.client, tx):
return false
true
proc sendNextTx*(env: TestEnv, tc: BaseTx): bool =
env.engine.sendNextTx(env.vaultKey, tc)
let client = env.engine.client
env.sender.sendNextTx(client, tc)
proc sendTx*(env: TestEnv, tc: BaseTx, nonce: AccountNonce): bool =
env.engine.sendTx(env.vaultKey, tc, nonce)
let client = env.engine.client
env.sender.sendTx(client, tc, nonce)
proc sendTx*(env: TestEnv, tc: BigInitcodeTx, nonce: AccountNonce): bool =
env.engine.sendTx(env.vaultKey, tc, nonce)
let client = env.engine.client
env.sender.sendTx(client, tc, nonce)
proc sendTx*(env: TestEnv, tx: Transaction): bool =
env.engine.sendTx(tx)
let client = env.engine.client
sendTx(client, tx)
proc verifyPoWProgress*(env: TestEnv, lastBlockHash: common.Hash256): bool =
let res = waitFor env.client.verifyPoWProgress(lastBlockHash)

View File

@ -0,0 +1,216 @@
import
std/[tables, math],
eth/keys,
stew/endians2,
nimcrypto/sha2,
chronicles,
./engine_client,
../../../nimbus/transaction,
../../../nimbus/common
type
BaseTx* = object of RootObj
recipient*: Option[EthAddress]
gasLimit* : GasInt
amount* : UInt256
payload* : seq[byte]
txType* : Option[TxType]
BigInitcodeTx* = object of BaseTx
initcodeLength*: int
padByte* : uint8
initcode* : seq[byte]
TestAccount = object
key : PrivateKey
address: EthAddress
index : int
TxSender* = ref object
accounts: seq[TestAccount]
nonceMap: Table[EthAddress, uint64]
txSent : int
chainId : ChainID
MakeTxParams* = object
chainId*: ChainID
key* : PrivateKey
nonce* : AccountNonce
const
TestAccountCount = 1000
func toAddress(key: PrivateKey): EthAddress =
toKeyPair(key).pubkey.toCanonicalAddress()
proc createAccount(idx: int): TestAccount =
let
seed = toBytesBE(idx.uint64)
seedHash = sha256.digest(seed)
result.index = idx
result.key = PrivateKey.fromRaw(seedHash.data).valueOr:
echo error
quit(QuitFailure)
result.address = toAddress(result.key)
proc createAccounts(sender: TxSender) =
for i in 0..<TestAccountCount:
sender.accounts.add createAccount(i.int)
proc getNextAccount(sender: TxSender): TestAccount =
sender.accounts[sender.txSent mod sender.accounts.len]
proc getNextNonce(sender: TxSender, address: EthAddress): uint64 =
let nonce = sender.nonceMap.getOrDefault(address, 0'u64)
sender.nonceMap[address] = nonce + 1
nonce
proc fillBalance(sender: TxSender, params: NetworkParams) =
for x in sender.accounts:
params.genesis.alloc[x.address] = GenesisAccount(
balance: UInt256.fromHex("0x123450000000000000000"),
)
proc new*(_: type TxSender, params: NetworkParams): TxSender =
result = TxSender(chainId: params.config.chainId)
result.createAccounts()
result.fillBalance(params)
func gwei(n: int64): GasInt {.compileTime.} =
GasInt(n * (10 ^ 9))
proc getTxType(tc: BaseTx, nonce: uint64): TxType =
if tc.txType.isNone:
if nonce mod 2 == 0:
TxLegacy
else:
TxEIP1559
else:
tc.txType.get
proc makeTx(params: MakeTxParams, tc: BaseTx): Transaction =
const
gasPrice = 30.gwei
gasTipPrice = 1.gwei
gasFeeCap = gasPrice
gasTipCap = gasTipPrice
let txType = tc.getTxType(params.nonce)
# Build the transaction depending on the specified type
let tx = if txType == TxLegacy:
Transaction(
txType : TxLegacy,
nonce : params.nonce,
to : tc.recipient,
value : tc.amount,
gasLimit: tc.gasLimit,
gasPrice: gasPrice,
payload : tc.payload
)
else:
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
)
signTransaction(tx, params.key, params.chainId, eip155 = true)
proc makeTx(params: MakeTxParams, tc: BigInitcodeTx): Transaction =
var tx = tc
if tx.payload.len == 0:
# Prepare initcode payload
if tx.initcode.len != 0:
doAssert(tx.initcode.len <= tx.initcodeLength, "invalid initcode (too big)")
tx.payload = tx.initcode
while tx.payload.len < tx.initcodeLength:
tx.payload.add tx.padByte
doAssert(tx.recipient.isNone, "invalid configuration for big contract tx creator")
params.makeTx(tx.BaseTx)
proc makeTx*(sender: TxSender, tc: BaseTx, nonce: AccountNonce): Transaction =
let acc = sender.getNextAccount()
let params = MakeTxParams(
chainId: sender.chainId,
key: acc.key,
nonce: nonce
)
params.makeTx(tc)
proc makeTx*(sender: TxSender, tc: BigInitcodeTx, nonce: AccountNonce): Transaction =
let acc = sender.getNextAccount()
let params = MakeTxParams(
chainId: sender.chainId,
key: acc.key,
nonce: nonce
)
params.makeTx(tc)
proc makeNextTx*(sender: TxSender, tc: BaseTx): Transaction =
let
acc = sender.getNextAccount()
nonce = sender.getNextNonce(acc.address)
params = MakeTxParams(
chainId: sender.chainId,
key: acc.key,
nonce: nonce
)
params.makeTx(tc)
proc sendNextTx*(sender: TxSender, client: RpcClient, tc: BaseTx): bool =
let tx = sender.makeNextTx(tc)
let rr = client.sendTransaction(tx)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
return true
proc sendTx*(sender: TxSender, client: RpcClient, tc: BaseTx, nonce: AccountNonce): bool =
let
acc = sender.getNextAccount()
params = MakeTxParams(
chainId: sender.chainId,
key: acc.key,
nonce: nonce
)
tx = params.makeTx(tc)
let rr = client.sendTransaction(tx)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
return true
proc sendTx*(sender: TxSender, client: RpcClient, tc: BigInitcodeTx, nonce: AccountNonce): bool =
let
acc = sender.getNextAccount()
params = MakeTxParams(
chainId: sender.chainId,
key: acc.key,
nonce: nonce
)
tx = params.makeTx(tc)
let rr = client.sendTransaction(tx)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
return true
proc sendTx*(client: RpcClient, tx: Transaction): bool =
let rr = client.sendTransaction(tx)
if rr.isErr:
error "Unable to send transaction", msg=rr.error
return false
return true

View File

@ -1,6 +1,7 @@
import
std/[options, typetraits, strutils],
eth/common,
stew/byteutils,
web3/ethtypes,
web3/engine_api_types,
../../../nimbus/beacon/execution_types,
@ -19,6 +20,7 @@ type
const
DefaultTimeout* = 60 # seconds
DefaultSleep* = 1
prevRandaoContractAddr* = hexToByteArray[20]("0000000000000000000000000000000000000316")
template testCond*(expr: untyped) =
if not (expr):
@ -85,6 +87,16 @@ template expectStatus*(res, cond: untyped) =
testCond s.status == PayloadExecutionStatus.cond:
error "Unexpected newPayload status", expect=PayloadExecutionStatus.cond, get=s.status
template expectStatusEither*(res, cond1, cond2: untyped) =
testCond res.isOk:
error "Unexpected newPayload error", msg=res.error
let s = res.get()
testCond s.status == PayloadExecutionStatus.cond1 or s.status == PayloadExecutionStatus.cond2:
error "Unexpected newPayload status",
expect1=PayloadExecutionStatus.cond1,
expect2=PayloadExecutionStatus.cond2,
get=s.status
template expectWithdrawalsRoot*(res: untyped, h: common.BlockHeader, wdRoot: Option[common.Hash256]) =
testCond res.isOk:
error "Unexpected error", msg=res.error

View File

@ -3,7 +3,7 @@ import
withdrawals/wd_block_value_spec,
withdrawals/wd_max_init_code_spec,
#withdrawals/wd_payload_body_spec,
#withdrawals/wd_reorg_spec,
withdrawals/wd_reorg_spec,
withdrawals/wd_sync_spec,
./types,
./test_env
@ -24,7 +24,72 @@ proc specExecute[T](ws: BaseSpec): bool =
let wdTestList* = [
#Re-Org tests
#[TestDesc(
TestDesc(
name: "Withdrawals Fork on Block 1 - 8 Block Re-Org, Sync",
about: "Tests a 8 block re-org using NewPayload. Re-org does not change withdrawals fork height",
run: specExecute[ReorgSpec],
spec: ReorgSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
timeoutSeconds: 300,
wdForkHeight: 1, # Genesis is Pre-Withdrawals
wdBlockCount: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
wdPerBlock: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
reOrgBlockCount: 8,
reOrgViaSync: true,
)),
TestDesc(
name: "Withdrawals Fork on Block 8 - 10 Block Re-Org Sync",
about: " Tests a 10 block re-org using sync",
# Re-org does not change withdrawals fork height, but changes
# the payload at the height of the fork
run: specExecute[ReorgSpec],
spec: ReorgSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
timeoutSeconds: 300,
wdForkHeight: 8, # Genesis is Pre-Withdrawals
wdBlockCount: 8,
wdPerBlock: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
reOrgBlockCount: 10,
reOrgViaSync: true,
)),
TestDesc(
name: "Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org Sync",
about: "Tests a 10 block re-org using sync",
# Sidechain reaches withdrawals fork at a lower block height
# than the canonical chain
run: specExecute[ReorgSpec],
spec: ReorgSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
timeoutSeconds: 300,
wdForkHeight: 8, # Genesis is Pre-Withdrawals
wdBlockCount: 8,
wdPerBlock: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
reOrgBlockCount: 10,
reOrgViaSync: true,
sidechaintimeIncrements: 2,
)),
TestDesc(
name: "Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org Sync",
about: "Tests a 10 block re-org using sync",
# Sidechain reaches withdrawals fork at a higher block height
# than the canonical chain
run: specExecute[ReorgSpec],
spec: ReorgSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
timeoutSeconds: 300,
wdForkHeight: 8, # Genesis is Pre-Withdrawals
wdBlockCount: 8,
wdPerBlock: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
timeIncrements: 2,
reOrgBlockCount: 10,
reOrgViaSync: true,
sidechaintimeIncrements: 1,
)),
TestDesc(
name: "Withdrawals Fork on Block 1 - 1 Block Re-Org",
about: "Tests a simple 1 block re-org",
run: specExecute[ReorgSpec],
@ -52,20 +117,6 @@ let wdTestList* = [
reOrgBlockCount: 8,
reOrgViaSync: false,
)),
TestDesc(
name: "Withdrawals Fork on Block 1 - 8 Block Re-Org, Sync",
about: "Tests a 8 block re-org using NewPayload. Re-org does not change withdrawals fork height",
run: specExecute[ReorgSpec],
spec: ReorgSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
timeoutSeconds: 300,
wdForkHeight: 1, # Genesis is Pre-Withdrawals
wdBlockCount: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
wdPerBlock: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
reOrgBlockCount: 8,
reOrgViaSync: true,
)),
TestDesc(
name: "Withdrawals Fork on Block 8 - 10 Block Re-Org NewPayload",
about: "Tests a 10 block re-org using NewPayload\n" &
@ -82,22 +133,6 @@ let wdTestList* = [
reOrgBlockCount: 10,
reOrgViaSync: false,
)),
TestDesc(
name: "Withdrawals Fork on Block 8 - 10 Block Re-Org Sync",
about: " Tests a 10 block re-org using sync",
# Re-org does not change withdrawals fork height, but changes
# the payload at the height of the fork
run: specExecute[ReorgSpec],
spec: ReorgSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
timeoutSeconds: 300,
wdForkHeight: 8, # Genesis is Pre-Withdrawals
wdBlockCount: 8,
wdPerBlock: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
reOrgBlockCount: 10,
reOrgViaSync: true,
)),
TestDesc(
name: "Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org",
about: "Tests a 10 block re-org using NewPayload",
@ -115,23 +150,6 @@ let wdTestList* = [
reOrgViaSync: false,
sidechaintimeIncrements: 2,
)),
TestDesc(
name: "Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org Sync",
about: "Tests a 10 block re-org using sync",
# Sidechain reaches withdrawals fork at a lower block height
# than the canonical chain
run: specExecute[ReorgSpec],
spec: ReorgSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
timeoutSeconds: 300,
wdForkHeight: 8, # Genesis is Pre-Withdrawals
wdBlockCount: 8,
wdPerBlock: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
reOrgBlockCount: 10,
reOrgViaSync: true,
sidechaintimeIncrements: 2,
)),
TestDesc(
name: "Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org",
about: "Tests a 10 block re-org using NewPayload",
@ -150,24 +168,6 @@ let wdTestList* = [
reOrgViaSync: false,
sidechaintimeIncrements: 1,
)),
TestDesc(
name: "Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org Sync",
about: "Tests a 10 block re-org using sync",
# Sidechain reaches withdrawals fork at a higher block height
# than the canonical chain
run: specExecute[ReorgSpec],
spec: ReorgSpec(
slotsToSafe: u256(32),
slotsToFinalized: u256(64),
timeoutSeconds: 300,
wdForkHeight: 8, # Genesis is Pre-Withdrawals
wdBlockCount: 8,
wdPerBlock: MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK,
timeIncrements: 2,
reOrgBlockCount: 10,
reOrgViaSync: true,
sidechaintimeIncrements: 1,
)),]#
# Sync Tests
TestDesc(

View File

@ -32,8 +32,8 @@ type
skipBaseVerifications*: bool # For code reuse of the base spec procedure
WithdrawalsForBlock = object
wds: seq[Withdrawal]
nextIndex: int
wds*: seq[Withdrawal]
nextIndex*: int
const
GenesisTimestamp = 0x1234
@ -46,13 +46,13 @@ const
]
# Get the per-block timestamp increments configured for this test
func getBlockTimeIncrements(ws: WDBaseSpec): int =
func getBlockTimeIncrements*(ws: WDBaseSpec): int =
if ws.timeIncrements == 0:
return 1
ws.timeIncrements
# Timestamp delta between genesis and the withdrawals fork
func getWithdrawalsGenesisTimeDelta(ws: WDBaseSpec): int =
func getWithdrawalsGenesisTimeDelta*(ws: WDBaseSpec): int =
ws.wdForkHeight * ws.getBlockTimeIncrements()
# Calculates Shanghai fork timestamp given the amount of blocks that need to be
@ -93,7 +93,7 @@ func addUnconditionalBytecode(g: Genesis, start, stop: UInt256) =
)
acc = acc + 1
func getWithdrawableAccountCount(ws: WDBaseSpec):int =
func getWithdrawableAccountCount*(ws: WDBaseSpec):int =
if ws.wdAbleAccountCount == 0:
# Withdraw to MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK accounts by default
return MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK
@ -163,7 +163,7 @@ func getGenesis*(ws: WDBaseSpec, param: NetworkParams): NetworkParams =
param
func getTransactionCountPerPayload(ws: WDBaseSpec): int =
func getTransactionCountPerPayload*(ws: WDBaseSpec): int =
ws.txPerBlock.get(16)
proc verifyContractsStorage(ws: WDBaseSpec, env: TestEnv): Result[void, string] =
@ -201,11 +201,11 @@ func getPreWithdrawalsBlockCount*(ws: WDBaseSpec): int =
ws.wdForkHeight - 1
# Number of payloads to be produced (pre and post withdrawals) during the entire test
func getTotalPayloadCount(ws: WDBaseSpec): int =
func getTotalPayloadCount*(ws: WDBaseSpec): int =
ws.getPreWithdrawalsBlockCount() + ws.wdBlockCount
# Generates a list of withdrawals based on current configuration
func generateWithdrawalsForBlock(ws: WDBaseSpec, nextIndex: int, startAccount: UInt256): WithdrawalsForBlock =
func generateWithdrawalsForBlock*(ws: WDBaseSpec, nextIndex: int, startAccount: UInt256): WithdrawalsForBlock =
let
differentAccounts = ws.getWithdrawableAccountCount()

View File

@ -1,11 +1,15 @@
import
std/tables,
stint,
chronos,
chronicles,
eth/common,
./wd_base_spec,
./wd_history,
../test_env,
../engine_client,
../types
../types,
../../../nimbus/beacon/web3_eth_conv
# Withdrawals re-org spec:
# Specifies a withdrawals test where the withdrawals re-org can happen
@ -13,311 +17,315 @@ import
# withdrawals block.
type
ReorgSpec* = ref object of WDBaseSpec
reOrgBlockCount* : uint64 # How many blocks the re-org will replace, including the head
reOrgViaSync* : bool # Whether the client should fetch the sidechain by syncing from the secondary client
sidechainTimeIncrements*: uint64
# How many blocks the re-org will replace, including the head
reOrgBlockCount* : int
# Whether the client should fetch the sidechain by syncing from the secondary client
reOrgViaSync* : bool
sidechainTimeIncrements*: int
slotsToSafe* : UInt256
slotsToFinalized* : UInt256
timeoutSeconds* : int
#[
func (ws *WithdrawalsReorgSpec) GetSidechainSplitHeight() uint64 {
if ws.ReOrgBlockCount > ws.getTotalPayloadCount() {
panic("invalid payload/re-org configuration")
return ws.getTotalPayloadCount() + 1 - ws.ReOrgBlockCount
Sidechain = ref object
startAccount: UInt256
nextIndex : int
wdHistory : WDHistory
sidechain : Table[uint64, ExecutionPayload]
payloadId : PayloadID
height : uint64
attr : Option[PayloadAttributes]
func (ws *WithdrawalsReorgSpec) GetSidechainBlockTimeIncrements() uint64 {
if ws.SidechainTimeIncrements == 0 {
Canonical = ref object
startAccount: UInt256
nextIndex : int
proc getSidechainSplitHeight(ws: ReorgSpec): int =
doAssert(ws.reOrgBlockCount <= ws.getTotalPayloadCount())
return ws.getTotalPayloadCount() + 1 - ws.reOrgBlockCount
proc getSidechainBlockTimeIncrements(ws: ReorgSpec): int=
if ws.sidechainTimeIncrements == 0:
return ws.getBlockTimeIncrements()
ws.sidechainTimeIncrements
return ws.SidechainTimeIncrements
proc getSidechainWdForkHeight(ws: ReorgSpec): int =
if ws.getSidechainBlockTimeIncrements() != ws.getBlockTimeIncrements():
# Block timestamp increments in both chains are different so need to
# calculate different heights, only if split happens before fork.
# We cannot split by having two different genesis blocks.
doAssert(ws.getSidechainSplitHeight() != 0, "invalid sidechain split height")
func (ws *WithdrawalsReorgSpec) GetSidechainWithdrawalsForkHeight() uint64 {
if ws.getSidechainBlockTimeIncrements() != ws.getBlockTimeIncrements() {
# Block timestamp increments in both chains are different so need to calculate different heights, only if split happens before fork
if ws.getSidechainSplitHeight() == 0 {
# We cannot split by having two different genesis blocks.
panic("invalid sidechain split height")
if ws.getSidechainSplitHeight() <= ws.WithdrawalsForkHeight {
if ws.getSidechainSplitHeight() <= ws.wdForkHeight:
# We need to calculate the height of the fork on the sidechain
sidechainSplitBlockTimestamp := ((ws.getSidechainSplitHeight() - 1) * ws.getBlockTimeIncrements())
remainingTime := (ws.getWithdrawalsGenesisTimeDelta() - sidechainSplitBlockTimestamp)
if remainingTime == 0 {
let sidechainSplitBlocktimestamp = (ws.getSidechainSplitHeight() - 1) * ws.getBlockTimeIncrements()
let remainingTime = ws.getWithdrawalsGenesisTimeDelta() - sidechainSplitBlocktimestamp
if remainingTime == 0 :
return ws.getSidechainSplitHeight()
return ((remainingTime - 1) / ws.SidechainTimeIncrements) + ws.getSidechainSplitHeight()
return ((remainingTime - 1) div ws.sidechainTimeIncrements) + ws.getSidechainSplitHeight()
return ws.WithdrawalsForkHeight
]#
return ws.wdForkHeight
proc execute*(ws: ReorgSpec, t: TestEnv): bool =
testCond waitFor t.clMock.waitForTTD()
proc execute*(ws: ReorgSpec, env: TestEnv): bool =
result = true
testCond waitFor env.clMock.waitForTTD()
return true
#[
# Spawn a secondary client which will produce the sidechain
secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles, t.Engine)
if err != nil {
error "Unable to spawn a secondary client: %v", t.TestName, err)
}
secondaryEngineTest := test.NewTestEngineClient(t, secondaryEngine)
# t.clMock.AddEngineClient(secondaryEngine)
let sec = env.addEngine(addToCL = false)
var (
canonicalStartAccount = big.NewInt(0x1000)
canonicalNextIndex = uint64(0)
sidechainStartAccount = new(big.Int).SetBit(common.Big0, 160, 1)
sidechainNextIndex = uint64(0)
sidechainwdHistory = make(wdHistory)
sidechain = make(map[uint64]*typ.ExecutableData)
sidechainPayloadId *beacon.PayloadID
)
var
canonical = Canonical(
startAccount: u256(0x1000),
nextIndex : 0,
)
sidechain = Sidechain(
startAccount: 1.u256 shl 160,
nextIndex : 0,
wdHistory : WDHistory(),
sidechain : initTable[uint64, ExecutionPayload]()
)
# Sidechain withdraws on the max account value range 0xffffffffffffffffffffffffffffffffffffffff
sidechainStartAccount.Sub(sidechainStartAccount, big.NewInt(int64(ws.getWithdrawableAccountCount())+1))
sidechain.startAccount -= u256(ws.getWithdrawableAccountCount()+1)
t.clMock.ProduceBlocks(int(ws.getPreWithdrawalsBlockCount()+ws.WithdrawalsBlockCount), clmock.BlockProcessCallbacks{
OnPayloadProducerSelected: proc(): bool =
t.clMock.NextWithdrawals = nil
let numBlocks = ws.getPreWithdrawalsBlockCount()+ws.wdBlockCount
let pbRes = env.clMock.produceBlocks(numBlocks, BlockProcessCallbacks(
onPayloadProducerSelected: proc(): bool =
env.clMock.nextWithdrawals = none(seq[WithdrawalV1])
if t.clMock.CurrentPayloadNumber >= ws.WithdrawalsForkHeight {
if env.clMock.currentPayloadNumber >= ws.wdForkHeight.uint64:
# Prepare some withdrawals
t.clMock.NextWithdrawals, canonicalNextIndex = ws.GenerateWithdrawalsForBlock(canonicalNextIndex, canonicalStartAccount)
ws.wdHistory[t.clMock.CurrentPayloadNumber] = t.clMock.NextWithdrawals
}
let wfb = ws.generateWithdrawalsForBlock(canonical.nextIndex, canonical.startAccount)
env.clMock.nextWithdrawals = some(w3Withdrawals wfb.wds)
canonical.nextIndex = wfb.nextIndex
ws.wdHistory.put(env.clMock.currentPayloadNumber, wfb.wds)
if t.clMock.CurrentPayloadNumber >= ws.getSidechainSplitHeight() {
if env.clMock.currentPayloadNumber >= ws.getSidechainSplitHeight().uint64:
# We have split
if t.clMock.CurrentPayloadNumber >= ws.getSidechainWithdrawalsForkHeight() {
if env.clMock.currentPayloadNumber >= ws.getSidechainWdForkHeight().uint64:
# And we are past the withdrawals fork on the sidechain
sidechainwdHistory[t.clMock.CurrentPayloadNumber], sidechainNextIndex = ws.GenerateWithdrawalsForBlock(sidechainNextIndex, sidechainStartAccount)
} # else nothing to do
} else {
# We have not split
sidechainwdHistory[t.clMock.CurrentPayloadNumber] = t.clMock.NextWithdrawals
sidechainNextIndex = canonicalNextIndex
}
let wfb = ws.generateWithdrawalsForBlock(sidechain.nextIndex, sidechain.startAccount)
sidechain.wdHistory.put(env.clMock.currentPayloadNumber, wfb.wds)
sidechain.nextIndex = wfb.nextIndex
else:
if env.clMock.nextWithdrawals.isSome:
let wds = ethWithdrawals env.clMock.nextWithdrawals.get()
sidechain.wdHistory.put(env.clMock.currentPayloadNumber, wds)
sidechain.nextIndex = canonical.nextIndex
},
OnRequestNextPayload: proc(): bool =
return true
,
onRequestNextPayload: proc(): bool =
# Send transactions to be included in the payload
txs, err := helper.SendNextTransactions(
t.TestContext,
t.clMock.NextBlockProducer,
&helper.BaseTransactionCreator{
Recipient: &globals.PrevRandaoContractAddr,
Amount: common.Big1,
Payload: nil,
TxType: t.TestTransactionType,
GasLimit: 75000,
},
ws.getTransactionCountPerPayload(),
let txs = env.makeTxs(
BaseTx(
recipient: some(prevRandaoContractAddr),
amount: 1.u256,
txType: ws.txType,
gasLimit: 75000.GasInt,
),
ws.getTransactionCountPerPayload()
)
if err != nil {
error "Error trying to send transactions: %v", t.TestName, err)
}
testCond env.sendTxs(env.clMock.nextBlockProducer, txs):
error "Error trying to send transaction"
# Error will be ignored here since the tx could have been already relayed
secondaryEngine.SendTransactions(t.TestContext, txs...)
discard env.sendTxs(sec, txs)
if t.clMock.CurrentPayloadNumber >= ws.getSidechainSplitHeight() {
if env.clMock.currentPayloadNumber >= ws.getSidechainSplitHeight().uint64:
# Also request a payload from the sidechain
fcU := beacon.ForkchoiceStateV1{
HeadBlockHash: t.clMock.latestForkchoice.HeadBlockHash,
}
var fcState = ForkchoiceStateV1(
headBlockHash: env.clMock.latestForkchoice.headBlockHash,
)
if t.clMock.CurrentPayloadNumber > ws.getSidechainSplitHeight() {
if lastSidePayload, ok := sidechain[t.clMock.CurrentPayloadNumber-1]; !ok {
panic("sidechain payload not found")
} else {
fcU.HeadBlockHash = lastSidePayload.BlockHash
}
}
if env.clMock.currentPayloadNumber > ws.getSidechainSplitHeight().uint64:
let lastSidePayload = sidechain.sidechain[env.clMock.currentPayloadNumber-1]
fcState.headBlockHash = lastSidePayload.blockHash
var version int
pAttributes := typ.PayloadAttributes{
Random: t.clMock.latestPayloadAttributes.Random,
SuggestedFeeRecipient: t.clMock.latestPayloadAttributes.SuggestedFeeRecipient,
}
if t.clMock.CurrentPayloadNumber > ws.getSidechainSplitHeight() {
pAttributes.Timestamp = sidechain[t.clMock.CurrentPayloadNumber-1].Timestamp + uint64(ws.getSidechainBlockTimeIncrements())
} else if t.clMock.CurrentPayloadNumber == ws.getSidechainSplitHeight() {
pAttributes.Timestamp = t.clMock.latestHeader.Time + uint64(ws.getSidechainBlockTimeIncrements())
} else {
pAttributes.Timestamp = t.clMock.latestPayloadAttributes.Timestamp
}
if t.clMock.CurrentPayloadNumber >= ws.getSidechainWithdrawalsForkHeight() {
var attr = PayloadAttributes(
prevRandao: env.clMock.latestPayloadAttributes.prevRandao,
suggestedFeeRecipient: env.clMock.latestPayloadAttributes.suggestedFeeRecipient,
)
if env.clMock.currentPayloadNumber > ws.getSidechainSplitHeight().uint64:
attr.timestamp = w3Qty(sidechain.sidechain[env.clMock.currentPayloadNumber-1].timestamp, ws.getSidechainBlockTimeIncrements())
elif env.clMock.currentPayloadNumber == ws.getSidechainSplitHeight().uint64:
attr.timestamp = w3Qty(env.clMock.latestHeader.timestamp, ws.getSidechainBlockTimeIncrements())
else:
attr.timestamp = env.clMock.latestPayloadAttributes.timestamp
if env.clMock.currentPayloadNumber >= ws.getSidechainwdForkHeight().uint64:
# Withdrawals
version = 2
pAttributes.Withdrawals = sidechainwdHistory[t.clMock.CurrentPayloadNumber]
} else {
# No withdrawals
version = 1
}
let rr = sidechain.wdHistory.get(env.clMock.currentPayloadNumber)
testCond rr.isOk:
error "sidechain wd", msg=rr.error
info "Requesting sidechain payload %d: %v", t.TestName, t.clMock.CurrentPayloadNumber, pAttributes)
attr.withdrawals = some(w3Withdrawals rr.get)
r := secondaryEngineTest.forkchoiceUpdated(&fcU, &pAttributes, version)
info "Requesting sidechain payload",
number=env.clMock.currentPayloadNumber
sidechain.attr = some(attr)
let r = sec.client.forkchoiceUpdated(fcState, attr)
r.expectNoError()
r.expectPayloadStatus(test.Valid)
if r.Response.PayloadID == nil {
error "Unable to get a payload ID on the sidechain", t.TestName)
}
sidechainPayloadId = r.Response.PayloadID
}
},
OnGetPayload: proc(): bool =
var (
version int
payload *typ.ExecutableData
)
if t.clMock.CurrentPayloadNumber >= ws.getSidechainWithdrawalsForkHeight() {
version = 2
} else {
version = 1
}
if t.clMock.latestPayloadBuilt.Number >= ws.getSidechainSplitHeight() {
r.testFCU(valid)
testCond r.get().payloadID.isSome:
error "Unable to get a payload ID on the sidechain"
sidechain.payloadId = r.get().payloadID.get()
return true
,
onGetPayload: proc(): bool =
var
payload: ExecutionPayload
if env.clMock.latestPayloadBuilt.blockNumber.uint64 >= ws.getSidechainSplitHeight().uint64:
# This payload is built by the secondary client, hence need to manually fetch it here
r := secondaryEngineTest.getPayload(sidechainPayloadId, version)
doAssert(sidechain.attr.isSome)
let version = sidechain.attr.get().version
let r = sec.client.getPayload(sidechain.payloadId, version)
r.expectNoError()
payload = &r.Payload
sidechain[payload.Number] = payload
} else {
payload = r.get().executionPayload
sidechain.sidechain[payload.blockNumber.uint64] = payload
else:
# This block is part of both chains, simply forward it to the secondary client
payload = &t.clMock.latestPayloadBuilt
}
r := secondaryEngineTest.newPayload(payload, nil, nil, version)
r.expectStatus(test.Valid)
p := secondaryEngineTest.forkchoiceUpdated(
&beacon.ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
},
nil,
version,
payload = env.clMock.latestPayloadBuilt
let r = sec.client.newPayload(payload, payload.version)
r.expectStatus(valid)
let fcState = ForkchoiceStateV1(
headBlockHash: payload.blockHash,
)
p.expectPayloadStatus(test.Valid)
},
})
let p = sec.client.forkchoiceUpdated(payload.version, fcState)
p.testFCU(valid)
return true
))
testCond pbRes
sidechainHeight := t.clMock.latestExecutedPayload.Number
sidechain.height = env.clMock.latestExecutedPayload.blockNumber.uint64
if ws.WithdrawalsForkHeight < ws.getSidechainWithdrawalsForkHeight() {
if ws.wdForkHeight < ws.getSidechainwdForkHeight():
# This means the canonical chain forked before the sidechain.
# Therefore we need to produce more sidechain payloads to reach
# at least`ws.WithdrawalsBlockCount` withdrawals payloads produced on
# the sidechain.
for i := uint64(0); i < ws.getSidechainWithdrawalsForkHeight()-ws.WithdrawalsForkHeight; i++ {
sidechainwdHistory[sidechainHeight+1], sidechainNextIndex = ws.GenerateWithdrawalsForBlock(sidechainNextIndex, sidechainStartAccount)
pAttributes := typ.PayloadAttributes{
Timestamp: sidechain[sidechainHeight].Timestamp + ws.getSidechainBlockTimeIncrements(),
Random: t.clMock.latestPayloadAttributes.Random,
SuggestedFeeRecipient: t.clMock.latestPayloadAttributes.SuggestedFeeRecipient,
Withdrawals: sidechainwdHistory[sidechainHeight+1],
}
r := secondaryEngineTest.forkchoiceUpdatedV2(&beacon.ForkchoiceStateV1{
HeadBlockHash: sidechain[sidechainHeight].BlockHash,
}, &pAttributes)
r.expectPayloadStatus(test.Valid)
time.Sleep(time.Second)
p := secondaryEngineTest.getPayloadV2(r.Response.PayloadID)
let height = ws.getSidechainwdForkHeight()-ws.wdForkHeight
for i in 0..<height:
let
wfb = ws.generateWithdrawalsForBlock(sidechain.nextIndex, sidechain.startAccount)
sidechain.wdHistory.put(sidechain.height+1, wfb.wds)
sidechain.nextIndex = wfb.nextIndex
let wds = sidechain.wdHistory.get(sidechain.height+1).valueOr:
echo "get wd history error ", error
return false
let
attr = PayloadAttributes(
timestamp: w3Qty(sidechain.sidechain[sidechain.height].timestamp, ws.getSidechainBlockTimeIncrements()),
prevRandao: env.clMock.latestPayloadAttributes.prevRandao,
suggestedFeeRecipient: env.clMock.latestPayloadAttributes.suggestedFeeRecipient,
withdrawals: some(w3Withdrawals wds),
)
fcState = ForkchoiceStateV1(
headBlockHash: sidechain.sidechain[sidechain.height].blockHash,
)
let r = sec.client.forkchoiceUpdatedV2(fcState, some(attr))
r.testFCU(valid)
let p = sec.client.getPayloadV2(r.get().payloadID.get)
p.expectNoError()
s := secondaryEngineTest.newPayloadV2(&p.Payload)
s.expectStatus(test.Valid)
q := secondaryEngineTest.forkchoiceUpdatedV2(
&beacon.ForkchoiceStateV1{
HeadBlockHash: p.Payload.BlockHash,
},
nil,
)
q.expectPayloadStatus(test.Valid)
sidechainHeight++
sidechain[sidechainHeight] = &p.Payload
}
}
let z = p.get()
let s = sec.client.newPayloadV2(z.executionPayload)
s.expectStatus(valid)
let fs = ForkchoiceStateV1(headBlockHash: z.executionPayload.blockHash)
let q = sec.client.forkchoiceUpdatedV2(fs)
q.testFCU(valid)
inc sidechain.height
sidechain.sidechain[sidechain.height] = executionPayload(z.executionPayload)
# Check the withdrawals on the latest
ws.wdHistory.VerifyWithdrawals(
sidechainHeight,
nil,
t.TestEngine,
)
let res = ws.wdHistory.verifyWithdrawals(sidechain.height, none(UInt256), env.client)
testCond res.isOk
if ws.ReOrgViaSync {
if ws.reOrgViaSync:
# Send latest sidechain payload as NewPayload + FCU and wait for sync
loop:
for {
r := t.rpcClient.newPayloadV2(sidechain[sidechainHeight])
let
payload = sidechain.sidechain[sidechain.height]
sideHash = sidechain.sidechain[sidechain.height].blockHash
sleep = DefaultSleep
period = chronos.seconds(sleep)
var loop = 0
if ws.timeoutSeconds == 0:
ws.timeoutSeconds = DefaultTimeout
while loop < ws.timeoutSeconds:
let r = env.client.newPayloadV2(payload.V2)
r.expectNoError()
p := t.rpcClient.forkchoiceUpdatedV2(
&beacon.ForkchoiceStateV1{
HeadBlockHash: sidechain[sidechainHeight].BlockHash,
},
nil,
)
let fcState = ForkchoiceStateV1(headBlockHash: sideHash)
let p = env.client.forkchoiceUpdatedV2(fcState)
p.expectNoError()
if p.Response.PayloadStatus.Status == test.Invalid {
error "Primary client invalidated side chain", t.TestName)
}
select {
case <-t.TimeoutContext.Done():
error "Timeout waiting for sync", t.TestName)
case <-time.After(time.Second):
b := t.rpcClient.BlockByNumber(nil)
if b.Block.Hash() == sidechain[sidechainHeight].BlockHash {
# sync successful
break loop
}
}
}
} else {
let status = p.get().payloadStatus.status
if status == PayloadExecutionStatus.invalid:
error "Primary client invalidated side chain"
return false
var header: common.BlockHeader
let b = env.client.latestHeader(header)
testCond b.isOk
if header.blockHash == ethHash(sidehash):
# sync successful
break
waitFor sleepAsync(period)
loop += sleep
else:
# Send all payloads one by one to the primary client
for payloadNumber := ws.getSidechainSplitHeight(); payloadNumber <= sidechainHeight; payloadNumber++ {
payload, ok := sidechain[payloadNumber]
if !ok {
error "Invalid payload %d requested.", t.TestName, payloadNumber)
}
var version int
if payloadNumber >= ws.getSidechainWithdrawalsForkHeight() {
version = 2
} else {
version = 1
}
info "Sending sidechain payload %d, hash=%s, parent=%s", t.TestName, payloadNumber, payload.BlockHash, payload.ParentHash)
r := t.rpcClient.newPayload(payload, nil, nil, version)
r.expectStatusEither(test.Valid, test.Accepted)
p := t.rpcClient.forkchoiceUpdated(
&beacon.ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
},
nil,
version,
)
p.expectPayloadStatus(test.Valid)
}
}
var payloadNumber = ws.getSidechainSplitHeight()
while payloadNumber.uint64 <= sidechain.height:
let payload = sidechain.sidechain[payloadNumber.uint64]
var version = Version.V1
if payloadNumber >= ws.getSidechainwdForkHeight():
version = Version.V2
info "Sending sidechain",
payloadNumber,
hash=payload.blockHash.short,
parentHash=payload.parentHash.short
let r = env.client.newPayload(payload, version)
r.expectStatusEither(valid, accepted)
let fcState = ForkchoiceStateV1(headBlockHash: payload.blockHash)
let p = env.client.forkchoiceUpdated(version, fcState)
p.testFCU(valid)
inc payloadNumber
# Verify withdrawals changed
sidechainwdHistory.VerifyWithdrawals(
sidechainHeight,
nil,
t.TestEngine,
)
let r2 = sidechain.wdHistory.verifyWithdrawals(sidechain.height, none(UInt256), env.client)
testCond r2.isOk
# Verify all balances of accounts in the original chain didn't increase
# after the fork.
# We are using different accounts credited between the canonical chain
# and the fork.
# We check on `latest`.
ws.wdHistory.VerifyWithdrawals(
ws.WithdrawalsForkHeight-1,
nil,
t.TestEngine,
)
let r3 = ws.wdHistory.verifyWithdrawals(uint64(ws.wdForkHeight-1), none(UInt256), env.client)
testCond r3.isOk
# Re-Org back to the canonical chain
r := t.rpcClient.forkchoiceUpdatedV2(&beacon.ForkchoiceStateV1{
HeadBlockHash: t.clMock.latestPayloadBuilt.BlockHash,
}, nil)
r.expectPayloadStatus(test.Valid)
]#
let fcState = ForkchoiceStateV1(headBlockHash: env.clMock.latestPayloadBuilt.blockHash)
let r = env.client.forkchoiceUpdatedV2(fcState)
r.testFCU(valid)

View File

@ -52,6 +52,11 @@ type
Option[PayloadAttributesV2] |
Option[PayloadAttributesV3]
GetPayloadResponse* = object
executionPayload*: ExecutionPayload
blockValue*: Option[UInt256]
blobsBundle*: Option[BlobsBundleV1]
Version* {.pure.} = enum
V1
V2
@ -59,21 +64,27 @@ type
func version*(payload: ExecutionPayload): Version =
if payload.blobGasUsed.isSome and payload.excessBlobGas.isSome:
return Version.V3
if payload.withdrawals.isSome:
return Version.V2
Version.V1
Version.V3
elif payload.withdrawals.isSome:
Version.V2
else:
Version.V1
func version*(attr: PayloadAttributes): Version =
if attr.parentBeaconBlockRoot.isSome:
return Version.V3
Version.V3
elif attr.withdrawals.isSome:
Version.V2
else:
Version.V1
if attr.withdrawals.isSome:
return Version.V2
Version.V1
func version*(res: GetPayloadResponse): Version =
if res.blobsBundle.isSome:
Version.V3
elif res.blockValue.isSome:
Version.V2
else:
Version.V1
func V1V2*(attr: PayloadAttributes): PayloadAttributesV1OrV2 =
PayloadAttributesV1OrV2(
@ -107,6 +118,22 @@ func V3*(attr: PayloadAttributes): PayloadAttributesV3 =
parentBeaconBlockRoot: attr.parentBeaconBlockRoot.get
)
func V1*(attr: Option[PayloadAttributes]): Option[PayloadAttributesV1] =
if attr.isNone:
return none(PayloadAttributesV1)
some(attr.get.V1)
when false:
func V2*(attr: Option[PayloadAttributes]): Option[PayloadAttributesV2] =
if attr.isNone:
return none(PayloadAttributesV2)
some(attr.get.V2)
func V3*(attr: Option[PayloadAttributes]): Option[PayloadAttributesV3] =
if attr.isNone:
return none(PayloadAttributesV3)
some(attr.get.V3)
func payloadAttributes*(attr: PayloadAttributesV1): PayloadAttributes =
PayloadAttributes(
timestamp: attr.timestamp,
@ -308,3 +335,19 @@ func executionPayload*(p: ExecutionPayloadV1OrV2): ExecutionPayload =
transactions: p.transactions,
withdrawals: p.withdrawals
)
func V1*(res: GetPayloadResponse): ExecutionPayloadV1 =
res.executionPayload.V1
func V2*(res: GetPayloadResponse): GetPayloadV2Response =
GetPayloadV2Response(
executionPayload: res.executionPayload.V1V2,
blockValue: res.blockValue.get
)
func V3*(res: GetPayloadResponse): GetPayloadV3Response =
GetPayloadV3Response(
executionPayload: res.executionPayload.V3,
blockValue: res.blockValue.get,
blobsBundle: res.blobsBundle.get
)

View File

@ -21,7 +21,7 @@ const
# maxTrackedHeaders is the maximum number of executed payloads the execution
# engine tracks before evicting old ones. Ideally we should only ever track
# the latest one; but have a slight wiggle room for non-ideal conditions.
MaxTrackedHeaders = 10
MaxTrackedHeaders = 96
type
QueueItem[T] = object

View File

@ -56,7 +56,7 @@ proc `$`*(x: Web3Quantity): string =
proc `$`*(x: Web3Address): string =
distinctBase(x).toHex
proc short*(x: Web3Hash): string =
let z = common.Hash256(data: distinctBase x)
short(z)
@ -168,6 +168,9 @@ func w3Qty*(x: common.EthTime): Web3Quantity =
func w3Qty*(x: common.EthTime, y: int): Web3Quantity =
Web3Quantity(x.toUnix + y.int64)
func w3Qty*(x: Web3Quantity, y: int): Web3Quantity =
Web3Quantity(x.uint64 + y.uint64)
func w3Qty*(x: Option[uint64]): Option[Web3Quantity] =
if x.isNone: none(Web3Quantity)
else: some(Web3Quantity x.get)

View File

@ -114,12 +114,17 @@ proc txFeesCovered(xp: TxPoolRef; item: TxItemRef): bool =
return false
true
import stew/byteutils
import ../../../utils/debug
proc txCostInBudget(xp: TxPoolRef; item: TxItemRef): bool =
## Check whether the worst case expense is covered by the price budget,
let
balance = xp.chain.getBalance(item.sender)
gasCost = item.tx.gasCost
if balance < gasCost:
debugEcho "nonce: ", item.tx.nonce, " ", balance, " ", gasCost, " ", item.sender.toHex
debugEcho debug(item.tx)
debug "invalid tx: not enough cash for gas",
available = balance,
require = gasCost

View File

@ -18,7 +18,6 @@ import
".."/[protocol, sync_desc],
./worker_desc,
./skeleton_main,
./skeleton_utils,
./beacon_impl
logScope: