324 lines
12 KiB
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)
|
|
]#
|