270 lines
8.9 KiB
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)
|
|
]#
|