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

270 lines
8.9 KiB
Nim

# Nimbus
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
import
chronos,
./wd_base_spec,
../test_env,
../engine_client,
../types
# Withdrawals sync spec:
# Specifies a withdrawals test where the withdrawals happen and then a
# client needs to sync and apply the withdrawals.
type
GetPayloadBodyRequest = ref object of RootObj
#Verify(int, *test.TestEngineClient, clmock.ExecutableDataHistory)
GetPayloadBodyRequestByRange = ref object of GetPayloadBodyRequest
Start uint64
Count uint64
GetPayloadBodyRequestByHashIndex = ref object of GetPayloadBodyRequest
BlockNumbers []uint64
Start uint64
End uint64
GetPayloadBodiesSpec* = ref object of WDBaseSpec
getPayloadBodiesRequests: seq[GetPayloadBodyRequest]
requestsRepeat : int
generateSidechain : bool
afterSync : bool
parallel : bool
#[
func (req GetPayloadBodyRequestByRange) Verify(reqIndex int, testEngine *test.TestEngineClient, payloadHistory clmock.ExecutableDataHistory) {
info "Starting GetPayloadBodyByRange request %d", reqIndex)
startTime := time.Now()
defer func() {
info "Ended GetPayloadBodyByRange request %d, %s", reqIndex, time.Since(startTime))
}()
r := testEngine.getPayloadBodiesByRangeV1(req.Start, req.Count)
if req.Start < 1 || req.Count < 1 {
r.expectationDescription = fmt.Sprintf(`
Sent start (%d) or count (%d) to engine_getPayloadBodiesByRangeV1 with a
value less than 1, therefore error is expected.
`, req.Start, req.Count)
r.expectErrorCode(InvalidParamsError)
return
}
latestPayloadNumber := payloadHistory.latestPayloadNumber()
if req.Start > latestPayloadNumber {
r.expectationDescription = fmt.Sprintf(`
Sent start=%d and count=%d to engine_getPayloadBodiesByRangeV1, latest known block is %d, hence an empty list is expected.
`, req.Start, req.Count, latestPayloadNumber)
r.expectPayloadBodiesCount(0)
} else {
var count = req.Count
if req.Start+req.Count-1 > latestPayloadNumber {
count = latestPayloadNumber - req.Start + 1
}
r.expectationDescription = fmt.Sprintf("Sent engine_getPayloadBodiesByRange(start=%d, count=%d), latest payload number in canonical chain is %d", req.Start, req.Count, latestPayloadNumber)
r.expectPayloadBodiesCount(count)
for i := req.Start; i < req.Start+count; i++ {
p := payloadHistory[i]
r.expectPayloadBody(i-req.Start, ExecutionPayloadBodyV1{
Transactions: p.Transactions,
Withdrawals: p.Withdrawals,
})
}
}
}
func (req GetPayloadBodyRequestByHashIndex) Verify(reqIndex int, testEngine *test.TestEngineClient, payloadHistory clmock.ExecutableDataHistory) {
info "Starting GetPayloadBodyByHash request %d", reqIndex)
startTime := time.Now()
defer func() {
info "Ended GetPayloadBodyByHash request %d, %s", reqIndex, time.Since(startTime))
}()
payloads := make([]ExecutableData, 0)
hashes := make([]common.Hash, 0)
if len(req.BlockNumbers) > 0 {
for _, n := range req.BlockNumbers {
if p, ok := payloadHistory[n]; ok {
payloads = append(payloads, p)
hashes = append(hashes, p.BlockHash)
} else {
# signal to request an unknown hash (random)
randHash := common.Hash{}
randomBytes(randHash[:])
payloads = append(payloads, nil)
hashes = append(hashes, randHash)
}
}
}
if req.Start > 0 && req.End > 0 {
for n := req.Start; n <= req.End; n++ {
if p, ok := payloadHistory[n]; ok {
payloads = append(payloads, p)
hashes = append(hashes, p.BlockHash)
} else {
# signal to request an unknown hash (random)
randHash := common.Hash{}
randomBytes(randHash[:])
payloads = append(payloads, nil)
hashes = append(hashes, randHash)
}
}
}
if len(payloads) == 0 {
panic("invalid test")
}
r := testEngine.TestEngineGetPayloadBodiesByHashV1(hashes)
r.expectPayloadBodiesCount(uint64(len(payloads)))
for i, p := range payloads {
var expectedPayloadBody ExecutionPayloadBodyV1
if p != nil {
expectedPayloadBody = ExecutionPayloadBodyV1{
Transactions: p.Transactions,
Withdrawals: p.Withdrawals,
}
}
r.expectPayloadBody(uint64(i), expectedPayloadBody)
}
}
]#
proc execute*(ws: GetPayloadBodiesSpec, t: TestEnv): bool =
WDBaseSpec(ws).skipBaseVerifications = true
testCond WDBaseSpec(ws).execute(t)
#[
payloadHistory := t.clMock.ExecutedPayloadHistory
testEngine := t.TestEngine
if ws.GenerateSidechain {
# First generate an extra payload on top of the canonical chain
# Generate more withdrawals
nextWithdrawals, _ := ws.GenerateWithdrawalsForBlock(payloadHistory.latestWithdrawalsIndex(), ws.getWithdrawalsStartAccount())
f := t.rpcClient.forkchoiceUpdatedV2(
&beacon.ForkchoiceStateV1{
HeadBlockHash: t.clMock.latestHeader.Hash(),
},
PayloadAttributes{
Timestamp: t.clMock.latestHeader.Time + ws.getBlockTimeIncrements(),
Withdrawals: nextWithdrawals,
},
)
f.expectPayloadStatus(PayloadExecutionStatus.valid)
# Wait for payload to be built
await sleepAsync(time.Second)
# Get the next canonical payload
p := t.rpcClient.getPayloadV2(f.Response.PayloadID)
p.expectNoError()
nextCanonicalPayload := &p.Payload
# Now we have an extra payload that follows the canonical chain,
# but we need a side chain for the test.
customizer := CustomPayloadData(
Withdrawals: RandomizeWithdrawalsOrder(t.clMock.latestExecutedPayload.Withdrawals),
}
sidechainCurrent, _, err := customizer.CustomizePayload(&t.clMock.latestExecutedPayload, t.clMock.latestPayloadAttributes.BeaconRoot)
if err != nil {
error "Error obtaining custom sidechain payload: %v", t.TestName, err)
}
customizer = CustomPayloadData(
ParentHash: &sidechainCurrent.BlockHash,
Withdrawals: RandomizeWithdrawalsOrder(nextCanonicalPayload.Withdrawals),
}
sidechainHead, _, err := customizer.CustomizePayload(nextCanonicalPayload, t.clMock.latestPayloadAttributes.BeaconRoot)
if err != nil {
error "Error obtaining custom sidechain payload: %v", t.TestName, err)
}
# Send both sidechain payloads as engine_newPayloadV2
n1 := t.rpcClient.newPayloadV2(sidechainCurrent)
n1.expectStatus(PayloadExecutionStatus.valid)
n2 := t.rpcClient.newPayloadV2(sidechainHead)
n2.expectStatus(PayloadExecutionStatus.valid)
} else if ws.AfterSync {
# Spawn a secondary client which will need to sync to the primary client
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)
loop:
for {
select {
case <-t.TimeoutContext.Done():
error "Timeout while waiting for secondary client to sync", t.TestName)
case <-time.After(time.Second):
secondaryEngineTest.newPayloadV2(
&t.clMock.latestExecutedPayload,
)
r := secondaryEngineTest.TestEngineForkchoiceUpdatedV2(
&t.clMock.latestForkchoice,
nil,
)
if r.Response.PayloadStatus.Status == PayloadExecutionStatus.valid {
break loop
}
if r.Response.PayloadStatus.Status == PayloadExecutionStatus.invalid {
error "Syncing client rejected valid chain: %s", t.TestName, r.Response)
}
}
}
# GetPayloadBodies will be sent to the secondary client
testEngine = secondaryEngineTest
}
# Now send the range request, which should ignore any sidechain
if ws.Parallel {
wg := new(sync.WaitGroup)
type RequestIndex struct {
Request GetPayloadBodyRequest
Index int
}
workChan := make(chan *RequestIndex)
workers := 16
wg.Add(workers)
for w := 0; w < workers; w++ {
go func() {
defer wg.Done()
for req := range workChan {
req.Request.Verify(req.Index, testEngine, payloadHistory)
}
}()
}
repeat := 1
if ws.RequestsRepeat > 0 {
repeat = ws.RequestsRepeat
}
for j := 0; j < repeat; j++ {
for i, req := range ws.getPayloadBodiesRequests {
workChan <- &RequestIndex{
Request: req,
Index: i + (j * repeat),
}
}
}
close(workChan)
wg.Wait()
} else {
for i, req := range ws.getPayloadBodiesRequests {
req.Verify(i, testEngine, payloadHistory)
]#