nimbus-eth1/hive_integration/nodocker/engine/withdrawals/wd_reorg_spec.nim

324 lines
12 KiB
Nim

import
stint,
chronos,
chronicles,
./wd_base_spec,
../test_env,
../engine_client,
../types
# Withdrawals re-org spec:
# Specifies a withdrawals test where the withdrawals re-org can happen
# even to a point before withdrawals were enabled, or simply to a previous
# 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
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
func (ws *WithdrawalsReorgSpec) GetSidechainBlockTimeIncrements() uint64 {
if ws.SidechainTimeIncrements == 0 {
return ws.getBlockTimeIncrements()
return ws.SidechainTimeIncrements
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 {
# 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 {
return ws.getSidechainSplitHeight()
return ((remainingTime - 1) / ws.SidechainTimeIncrements) + ws.getSidechainSplitHeight()
return ws.WithdrawalsForkHeight
]#
proc execute*(ws: ReorgSpec, t: TestEnv): bool =
testCond waitFor t.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)
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
)
# Sidechain withdraws on the max account value range 0xffffffffffffffffffffffffffffffffffffffff
sidechainStartAccount.Sub(sidechainStartAccount, big.NewInt(int64(ws.getWithdrawableAccountCount())+1))
t.clMock.ProduceBlocks(int(ws.getPreWithdrawalsBlockCount()+ws.WithdrawalsBlockCount), clmock.BlockProcessCallbacks{
OnPayloadProducerSelected: proc(): bool =
t.clMock.NextWithdrawals = nil
if t.clMock.CurrentPayloadNumber >= ws.WithdrawalsForkHeight {
# Prepare some withdrawals
t.clMock.NextWithdrawals, canonicalNextIndex = ws.GenerateWithdrawalsForBlock(canonicalNextIndex, canonicalStartAccount)
ws.wdHistory[t.clMock.CurrentPayloadNumber] = t.clMock.NextWithdrawals
}
if t.clMock.CurrentPayloadNumber >= ws.getSidechainSplitHeight() {
# We have split
if t.clMock.CurrentPayloadNumber >= ws.getSidechainWithdrawalsForkHeight() {
# 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
}
},
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(),
)
if err != nil {
error "Error trying to send transactions: %v", t.TestName, err)
}
# Error will be ignored here since the tx could have been already relayed
secondaryEngine.SendTransactions(t.TestContext, txs...)
if t.clMock.CurrentPayloadNumber >= ws.getSidechainSplitHeight() {
# Also request a payload from the sidechain
fcU := beacon.ForkchoiceStateV1{
HeadBlockHash: t.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
}
}
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() {
# Withdrawals
version = 2
pAttributes.Withdrawals = sidechainwdHistory[t.clMock.CurrentPayloadNumber]
} else {
# No withdrawals
version = 1
}
info "Requesting sidechain payload %d: %v", t.TestName, t.clMock.CurrentPayloadNumber, pAttributes)
r := secondaryEngineTest.forkchoiceUpdated(&fcU, &pAttributes, version)
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() {
# This payload is built by the secondary client, hence need to manually fetch it here
r := secondaryEngineTest.getPayload(sidechainPayloadId, version)
r.expectNoError()
payload = &r.Payload
sidechain[payload.Number] = 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,
)
p.expectPayloadStatus(test.Valid)
},
})
sidechainHeight := t.clMock.latestExecutedPayload.Number
if ws.WithdrawalsForkHeight < ws.getSidechainWithdrawalsForkHeight() {
# 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)
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
}
}
# Check the withdrawals on the latest
ws.wdHistory.VerifyWithdrawals(
sidechainHeight,
nil,
t.TestEngine,
)
if ws.ReOrgViaSync {
# Send latest sidechain payload as NewPayload + FCU and wait for sync
loop:
for {
r := t.rpcClient.newPayloadV2(sidechain[sidechainHeight])
r.expectNoError()
p := t.rpcClient.forkchoiceUpdatedV2(
&beacon.ForkchoiceStateV1{
HeadBlockHash: sidechain[sidechainHeight].BlockHash,
},
nil,
)
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 {
# 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)
}
}
# Verify withdrawals changed
sidechainwdHistory.VerifyWithdrawals(
sidechainHeight,
nil,
t.TestEngine,
)
# 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,
)
# Re-Org back to the canonical chain
r := t.rpcClient.forkchoiceUpdatedV2(&beacon.ForkchoiceStateV1{
HeadBlockHash: t.clMock.latestPayloadBuilt.BlockHash,
}, nil)
r.expectPayloadStatus(test.Valid)
]#