From 162eb3ffcad3d2804855ca4f60f3b76abf284322 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 17 Nov 2022 19:14:14 -0600 Subject: [PATCH 01/49] clients/go-ethereum: Changes to use withdrawals branch --- clients/go-ethereum/Dockerfile | 38 +++++++++++++++++----------------- clients/go-ethereum/mapper.jq | 1 + 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/clients/go-ethereum/Dockerfile b/clients/go-ethereum/Dockerfile index 27564e7d18..e702a44de2 100644 --- a/clients/go-ethereum/Dockerfile +++ b/clients/go-ethereum/Dockerfile @@ -4,29 +4,29 @@ # produce a very minimalistic container that can be reused many times without # needing to constantly rebuild. -ARG branch=latest -FROM ethereum/client-go:$branch +ARG branch=withdrawals-timestamp +# FROM ethereum/client-go:$branch -RUN apk add --update bash curl jq +# RUN apk add --update bash curl jq -# FROM alpine:latest -# ARG branch=master +FROM alpine:latest +ARG branch=master # Build go-ethereum on the fly and delete all build tools afterwards -# RUN \ -# apk add --update bash curl jq go git make gcc musl-dev \ -# ca-certificates linux-headers && \ -# git clone --depth 1 --branch $branch https://github.com/ethereum/go-ethereum && \ -# (cd go-ethereum && make geth) && \ -# (cd go-ethereum && \ -# echo "{}" \ -# | jq ".+ {\"repo\":\"$(git config --get remote.origin.url)\"}" \ -# | jq ".+ {\"branch\":\"$(git rev-parse --abbrev-ref HEAD)\"}" \ -# | jq ".+ {\"commit\":\"$(git rev-parse HEAD)\"}" \ -# > /version.json) && \ -# cp go-ethereum/build/bin/geth /geth && \ -# apk del go git make gcc musl-dev linux-headers && \ -# rm -rf /go-ethereum && rm -rf /var/cache/apk/* +RUN \ + apk add --update bash curl jq go git make gcc musl-dev \ + ca-certificates linux-headers && \ + git clone --depth 1 --branch withdrawals-timestamp https://github.com/lightclient/go-ethereum && \ + (cd go-ethereum && make geth) && \ + (cd go-ethereum && \ + echo "{}" \ + | jq ".+ {\"repo\":\"$(git config --get remote.origin.url)\"}" \ + | jq ".+ {\"branch\":\"$(git rev-parse --abbrev-ref HEAD)\"}" \ + | jq ".+ {\"commit\":\"$(git rev-parse HEAD)\"}" \ + > /version.json) && \ + cp go-ethereum/build/bin/geth /usr/local/bin/geth && \ + apk del go git make gcc musl-dev linux-headers && \ + rm -rf /go-ethereum && rm -rf /var/cache/apk/* RUN /usr/local/bin/geth console --exec 'console.log(admin.nodeInfo.name)' --maxpeers=0 --nodiscover --dev 2>/dev/null | head -1 > /version.txt diff --git a/clients/go-ethereum/mapper.jq b/clients/go-ethereum/mapper.jq index d416bd6c3c..87d899453c 100644 --- a/clients/go-ethereum/mapper.jq +++ b/clients/go-ethereum/mapper.jq @@ -54,5 +54,6 @@ def to_bool: "londonBlock": env.HIVE_FORK_LONDON|to_int, "mergeForkBlock": env.HIVE_MERGE_BLOCK_ID|to_int, "terminalTotalDifficulty": env.HIVE_TERMINAL_TOTAL_DIFFICULTY|to_int, + "shanghaiBlock": env.HIVE_SHANGHAI_TIMESTAMP|to_int, }|remove_empty } From adca3d39b89fa26a5a186191b2d487f01ee3b9e2 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 17 Nov 2022 19:20:49 -0600 Subject: [PATCH 02/49] simulators/ethereum/engine: Withdrawals Sanity test --- go.work.sum | 2 + simulators/ethereum/engine/client/engine.go | 14 +-- .../engine/client/hive_rpc/hive_rpc.go | 50 ++++++++--- .../ethereum/engine/client/node/node.go | 45 +++++++--- simulators/ethereum/engine/clmock/clmock.go | 89 +++++++++++++++---- simulators/ethereum/engine/go.mod | 2 + simulators/ethereum/engine/go.sum | 3 + simulators/ethereum/engine/helper/helper.go | 3 +- simulators/ethereum/engine/helper/payload.go | 19 +++- simulators/ethereum/engine/main.go | 17 +++- .../ethereum/engine/suites/engine/tests.go | 56 ++++++------ .../ethereum/engine/suites/sync/tests.go | 2 +- .../engine/suites/withdrawals/tests.go | 53 +++++++++++ simulators/ethereum/engine/test/env.go | 10 +-- simulators/ethereum/engine/test/expect.go | 39 +++++++- simulators/ethereum/engine/test/spec.go | 3 + 16 files changed, 323 insertions(+), 84 deletions(-) create mode 100644 simulators/ethereum/engine/suites/withdrawals/tests.go diff --git a/go.work.sum b/go.work.sum index 04f870e551..f7f1dd59f3 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,4 +1,6 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio= +github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342 h1:lSsZMLsNkER1eVnYJ67cUhWcK82amzDPsVyAQ/JJ9p8= +github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= diff --git a/simulators/ethereum/engine/client/engine.go b/simulators/ethereum/engine/client/engine.go index 767063688f..d59ca53a8a 100644 --- a/simulators/ethereum/engine/client/engine.go +++ b/simulators/ethereum/engine/client/engine.go @@ -24,12 +24,16 @@ type Eth interface { } type Engine interface { - ForkchoiceUpdatedV1(ctx context.Context, fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributesV1) (api.ForkChoiceResponse, error) - GetPayloadV1(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableDataV1, error) - NewPayloadV1(ctx context.Context, payload *api.ExecutableDataV1) (api.PayloadStatusV1, error) + ForkchoiceUpdatedV1(ctx context.Context, fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) (api.ForkChoiceResponse, error) + ForkchoiceUpdatedV2(ctx context.Context, fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) (api.ForkChoiceResponse, error) - LatestForkchoiceSent() (fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributesV1) - LatestNewPayloadSent() (payload *api.ExecutableDataV1) + GetPayloadV1(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, error) + GetPayloadV2(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, error) + NewPayloadV1(ctx context.Context, payload *api.ExecutableData) (api.PayloadStatusV1, error) + NewPayloadV2(ctx context.Context, payload *api.ExecutableData) (api.PayloadStatusV1, error) + + LatestForkchoiceSent() (fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) + LatestNewPayloadSent() (payload *api.ExecutableData) LatestForkchoiceResponse() (fcuResponse *api.ForkChoiceResponse) LatestNewPayloadResponse() (payloadResponse *api.PayloadStatusV1) diff --git a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go index b75af5c040..abe2b5ccab 100644 --- a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go +++ b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go @@ -152,10 +152,10 @@ type HiveRPCEngineClient struct { // Engine updates info latestFcUStateSent *api.ForkchoiceStateV1 - latestPAttrSent *api.PayloadAttributesV1 + latestPAttrSent *api.PayloadAttributes latestFcUResponse *api.ForkChoiceResponse - latestPayloadSent *api.ExecutableDataV1 + latestPayloadSent *api.ExecutableData latestPayloadStatusReponse *api.PayloadStatusV1 // Test account nonces @@ -293,38 +293,66 @@ func (ec *HiveRPCEngineClient) PrepareDefaultAuthCallToken() error { } // Engine API Call Methods -func (ec *HiveRPCEngineClient) ForkchoiceUpdatedV1(ctx context.Context, fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributesV1) (api.ForkChoiceResponse, error) { +func (ec *HiveRPCEngineClient) ForkchoiceUpdated(ctx context.Context, version int, fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) (api.ForkChoiceResponse, error) { var result api.ForkChoiceResponse if err := ec.PrepareDefaultAuthCallToken(); err != nil { return result, err } ec.latestFcUStateSent = fcState ec.latestPAttrSent = pAttributes - err := ec.c.CallContext(ctx, &result, "engine_forkchoiceUpdatedV1", fcState, pAttributes) + err := ec.c.CallContext(ctx, + &result, + fmt.Sprintf("engine_forkchoiceUpdatedV%d", version), + fcState, + pAttributes) ec.latestFcUResponse = &result return result, err } -func (ec *HiveRPCEngineClient) GetPayloadV1(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableDataV1, error) { - var result api.ExecutableDataV1 +func (ec *HiveRPCEngineClient) ForkchoiceUpdatedV1(ctx context.Context, fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) (api.ForkChoiceResponse, error) { + return ec.ForkchoiceUpdated(ctx, 1, fcState, pAttributes) +} + +func (ec *HiveRPCEngineClient) ForkchoiceUpdatedV2(ctx context.Context, fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) (api.ForkChoiceResponse, error) { + return ec.ForkchoiceUpdated(ctx, 2, fcState, pAttributes) +} + +func (ec *HiveRPCEngineClient) GetPayload(ctx context.Context, version int, payloadId *api.PayloadID) (api.ExecutableData, error) { + var result api.ExecutableData if err := ec.PrepareDefaultAuthCallToken(); err != nil { return result, err } - err := ec.c.CallContext(ctx, &result, "engine_getPayloadV1", payloadId) + err := ec.c.CallContext(ctx, &result, fmt.Sprintf("engine_getPayloadV%d", version), payloadId) return result, err } -func (ec *HiveRPCEngineClient) NewPayloadV1(ctx context.Context, payload *api.ExecutableDataV1) (api.PayloadStatusV1, error) { +func (ec *HiveRPCEngineClient) GetPayloadV1(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, error) { + return ec.GetPayload(ctx, 1, payloadId) +} + +func (ec *HiveRPCEngineClient) GetPayloadV2(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, error) { + return ec.GetPayload(ctx, 2, payloadId) +} + +func (ec *HiveRPCEngineClient) NewPayload(ctx context.Context, version int, payload *api.ExecutableData) (api.PayloadStatusV1, error) { var result api.PayloadStatusV1 if err := ec.PrepareDefaultAuthCallToken(); err != nil { return result, err } ec.latestPayloadSent = payload - err := ec.c.CallContext(ctx, &result, "engine_newPayloadV1", payload) + err := ec.c.CallContext(ctx, &result, fmt.Sprintf("engine_newPayloadV%d", version), payload) ec.latestPayloadStatusReponse = &result return result, err } +func (ec *HiveRPCEngineClient) NewPayloadV1(ctx context.Context, payload *api.ExecutableData) (api.PayloadStatusV1, error) { + return ec.NewPayload(ctx, 1, payload) +} + +func (ec *HiveRPCEngineClient) NewPayloadV2(ctx context.Context, payload *api.ExecutableData) (api.PayloadStatusV1, error) { + return ec.NewPayload(ctx, 2, payload) +} + func (ec *HiveRPCEngineClient) ExchangeTransitionConfigurationV1(ctx context.Context, tConf *api.TransitionConfigurationV1) (api.TransitionConfigurationV1, error) { var result api.TransitionConfigurationV1 err := ec.c.CallContext(ctx, &result, "engine_exchangeTransitionConfigurationV1", tConf) @@ -366,11 +394,11 @@ func (ec *HiveRPCEngineClient) PostRunVerifications() error { return nil } -func (ec *HiveRPCEngineClient) LatestForkchoiceSent() (fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributesV1) { +func (ec *HiveRPCEngineClient) LatestForkchoiceSent() (fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) { return ec.latestFcUStateSent, ec.latestPAttrSent } -func (ec *HiveRPCEngineClient) LatestNewPayloadSent() *api.ExecutableDataV1 { +func (ec *HiveRPCEngineClient) LatestNewPayloadSent() *api.ExecutableData { return ec.latestPayloadSent } diff --git a/simulators/ethereum/engine/client/node/node.go b/simulators/ethereum/engine/client/node/node.go index 7db6a552e6..9e2a83b624 100644 --- a/simulators/ethereum/engine/client/node/node.go +++ b/simulators/ethereum/engine/client/node/node.go @@ -19,6 +19,7 @@ import ( "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth" @@ -190,10 +191,10 @@ type GethNode struct { // Engine updates info latestFcUStateSent *beacon.ForkchoiceStateV1 - latestPAttrSent *beacon.PayloadAttributesV1 + latestPAttrSent *beacon.PayloadAttributes latestFcUResponse *beacon.ForkChoiceResponse - latestPayloadSent *beacon.ExecutableDataV1 + latestPayloadSent *beacon.ExecutableData latestPayloadStatusReponse *beacon.PayloadStatusV1 // Test specific configuration @@ -237,7 +238,7 @@ func restart(startConfig GethNodeTestConfiguration, bootnodes []string, datadir SyncMode: downloader.FullSync, DatabaseCache: 256, DatabaseHandles: 256, - TxPool: core.DefaultTxPoolConfig, + TxPool: txpool.DefaultConfig, GPO: ethconfig.Defaults.GPO, Ethash: ethconfig.Defaults.Ethash, Miner: ethconfig.Defaults.Miner, @@ -383,7 +384,7 @@ func (n *GethNode) PrepareNextBlock(currentBlock *types.Block) (*state.StateDB, if n.eth.BlockChain().Config().IsLondon(nextHeader.Number) { nextHeader.BaseFee = misc.CalcBaseFee(n.eth.BlockChain().Config(), currentBlock.Header()) if !n.eth.BlockChain().Config().IsLondon(currentBlock.Number()) { - parentGasLimit := currentBlock.GasLimit() * params.ElasticityMultiplier + parentGasLimit := currentBlock.GasLimit() * n.eth.BlockChain().Config().ElasticityMultiplier() nextHeader.GasLimit = parentGasLimit } } @@ -397,7 +398,7 @@ func (n *GethNode) PrepareNextBlock(currentBlock *types.Block) (*state.StateDB, if err != nil { panic(err) } - b, err := n.ethashEngine.FinalizeAndAssemble(n.eth.BlockChain(), nextHeader, state, nil, nil, nil) + b, err := n.ethashEngine.FinalizeAndAssemble(n.eth.BlockChain(), nextHeader, state, nil, nil, nil, nil) return state, b, err } @@ -688,14 +689,20 @@ func (n *GethNode) SetBlock(block *types.Block, parentNumber uint64, parentRoot } // Engine API -func (n *GethNode) NewPayloadV1(ctx context.Context, pl *beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) { +func (n *GethNode) NewPayloadV1(ctx context.Context, pl *beacon.ExecutableData) (beacon.PayloadStatusV1, error) { n.latestPayloadSent = pl resp, err := n.api.NewPayloadV1(*pl) n.latestPayloadStatusReponse = &resp return resp, err } +func (n *GethNode) NewPayloadV2(ctx context.Context, pl *beacon.ExecutableData) (beacon.PayloadStatusV1, error) { + n.latestPayloadSent = pl + resp, err := n.api.NewPayloadV2(*pl) + n.latestPayloadStatusReponse = &resp + return resp, err +} -func (n *GethNode) ForkchoiceUpdatedV1(ctx context.Context, fcs *beacon.ForkchoiceStateV1, payload *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { +func (n *GethNode) ForkchoiceUpdatedV1(ctx context.Context, fcs *beacon.ForkchoiceStateV1, payload *beacon.PayloadAttributes) (beacon.ForkChoiceResponse, error) { n.latestFcUStateSent = fcs n.latestPAttrSent = payload fcr, err := n.api.ForkchoiceUpdatedV1(*fcs, payload) @@ -703,10 +710,26 @@ func (n *GethNode) ForkchoiceUpdatedV1(ctx context.Context, fcs *beacon.Forkchoi return fcr, err } -func (n *GethNode) GetPayloadV1(ctx context.Context, payloadId *beacon.PayloadID) (beacon.ExecutableDataV1, error) { +func (n *GethNode) ForkchoiceUpdatedV2(ctx context.Context, fcs *beacon.ForkchoiceStateV1, payload *beacon.PayloadAttributes) (beacon.ForkChoiceResponse, error) { + n.latestFcUStateSent = fcs + n.latestPAttrSent = payload + fcr, err := n.api.ForkchoiceUpdatedV2(*fcs, payload) + n.latestFcUResponse = &fcr + return fcr, err +} + +func (n *GethNode) GetPayloadV1(ctx context.Context, payloadId *beacon.PayloadID) (beacon.ExecutableData, error) { + p, err := n.api.GetPayloadV1(*payloadId) + if p == nil || err != nil { + return beacon.ExecutableData{}, err + } + return *p, err +} + +func (n *GethNode) GetPayloadV2(ctx context.Context, payloadId *beacon.PayloadID) (beacon.ExecutableData, error) { p, err := n.api.GetPayloadV1(*payloadId) if p == nil || err != nil { - return beacon.ExecutableDataV1{}, err + return beacon.ExecutableData{}, err } return *p, err } @@ -878,11 +901,11 @@ func (n *GethNode) PostRunVerifications() error { return nil } -func (n *GethNode) LatestForkchoiceSent() (fcState *beacon.ForkchoiceStateV1, pAttributes *beacon.PayloadAttributesV1) { +func (n *GethNode) LatestForkchoiceSent() (fcState *beacon.ForkchoiceStateV1, pAttributes *beacon.PayloadAttributes) { return n.latestFcUStateSent, n.latestPAttrSent } -func (n *GethNode) LatestNewPayloadSent() (payload *beacon.ExecutableDataV1) { +func (n *GethNode) LatestNewPayloadSent() (payload *beacon.ExecutableData) { return n.latestPayloadSent } diff --git a/simulators/ethereum/engine/clmock/clmock.go b/simulators/ethereum/engine/clmock/clmock.go index a4e7f97c8c..e0ffdb3859 100644 --- a/simulators/ethereum/engine/clmock/clmock.go +++ b/simulators/ethereum/engine/clmock/clmock.go @@ -48,15 +48,15 @@ type CLMocker struct { // PoS Chain History Information PrevRandaoHistory map[uint64]common.Hash - ExecutedPayloadHistory map[uint64]api.ExecutableDataV1 + ExecutedPayloadHistory map[uint64]api.ExecutableData HeadHashHistory []common.Hash // Latest broadcasted data using the PoS Engine API LatestHeadNumber *big.Int LatestHeader *types.Header - LatestPayloadBuilt api.ExecutableDataV1 - LatestPayloadAttributes api.PayloadAttributesV1 - LatestExecutedPayload api.ExecutableDataV1 + LatestPayloadBuilt api.ExecutableData + LatestPayloadAttributes api.PayloadAttributes + LatestExecutedPayload api.ExecutableData LatestForkchoice api.ForkchoiceStateV1 // Merge related @@ -65,12 +65,20 @@ type CLMocker struct { TransitionPayloadTimestamp *big.Int SafeSlotsToImportOptimistically *big.Int + // Shanghai Related + ShanghaiTimestamp *big.Int + NextWithdrawals types.Withdrawals + // Global context which all procedures shall stop TestContext context.Context TimeoutContext context.Context } -func NewCLMocker(t *hivesim.T, slotsToSafe, slotsToFinalized, safeSlotsToImportOptimistically *big.Int) *CLMocker { +func isShanghai(blockTimestamp uint64, shanghaiTimestamp *big.Int) bool { + return shanghaiTimestamp != nil && big.NewInt(int64(blockTimestamp)).Cmp(shanghaiTimestamp) >= 0 +} + +func NewCLMocker(t *hivesim.T, slotsToSafe, slotsToFinalized, safeSlotsToImportOptimistically *big.Int, shanghaiTime *big.Int) *CLMocker { // Init random seed for different purposes seed := time.Now().Unix() t.Logf("Randomness seed: %v\n", seed) @@ -89,7 +97,7 @@ func NewCLMocker(t *hivesim.T, slotsToSafe, slotsToFinalized, safeSlotsToImportO T: t, EngineClients: make([]client.EngineClient, 0), PrevRandaoHistory: map[uint64]common.Hash{}, - ExecutedPayloadHistory: map[uint64]api.ExecutableDataV1{}, + ExecutedPayloadHistory: map[uint64]api.ExecutableData{}, SlotsToSafe: slotsToSafe, SlotsToFinalized: slotsToFinalized, SafeSlotsToImportOptimistically: safeSlotsToImportOptimistically, @@ -104,7 +112,8 @@ func NewCLMocker(t *hivesim.T, slotsToSafe, slotsToFinalized, safeSlotsToImportO SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, }, - TestContext: context.Background(), + ShanghaiTimestamp: shanghaiTime, + TestContext: context.Background(), } return newCLMocker @@ -244,12 +253,16 @@ func (cl *CLMocker) pickNextPayloadProducer() { } } +func (cl *CLMocker) SetNextWithdrawals(nextWithdrawals types.Withdrawals) { + cl.NextWithdrawals = nextWithdrawals +} + func (cl *CLMocker) GetNextPayloadID() { // Generate a random value for the PrevRandao field nextPrevRandao := common.Hash{} rand.Read(nextPrevRandao[:]) - cl.LatestPayloadAttributes = api.PayloadAttributesV1{ + cl.LatestPayloadAttributes = api.PayloadAttributes{ Random: nextPrevRandao, SuggestedFeeRecipient: cl.NextFeeRecipient, } @@ -261,12 +274,25 @@ func (cl *CLMocker) GetNextPayloadID() { cl.LatestPayloadAttributes.Timestamp = cl.LatestHeader.Time + 1 } + if isShanghai(cl.LatestPayloadAttributes.Timestamp, cl.ShanghaiTimestamp) && cl.NextWithdrawals != nil { + cl.LatestPayloadAttributes.Withdrawals = cl.NextWithdrawals + } + // Save random value cl.PrevRandaoHistory[cl.LatestHeader.Number.Uint64()+1] = nextPrevRandao ctx, cancel := context.WithTimeout(cl.TestContext, globals.RPCTimeout) defer cancel() - resp, err := cl.NextBlockProducer.ForkchoiceUpdatedV1(ctx, &cl.LatestForkchoice, &cl.LatestPayloadAttributes) + var ( + resp api.ForkChoiceResponse + err error + ) + if isShanghai(cl.LatestPayloadAttributes.Timestamp, cl.ShanghaiTimestamp) { + resp, err = cl.NextBlockProducer.ForkchoiceUpdatedV2(ctx, &cl.LatestForkchoice, &cl.LatestPayloadAttributes) + + } else { + resp, err = cl.NextBlockProducer.ForkchoiceUpdatedV1(ctx, &cl.LatestForkchoice, &cl.LatestPayloadAttributes) + } if err != nil { cl.Fatalf("CLMocker: Could not send forkchoiceUpdatedV1 (%v): %v", cl.NextBlockProducer.ID(), err) } @@ -283,7 +309,12 @@ func (cl *CLMocker) GetNextPayload() { var err error ctx, cancel := context.WithTimeout(cl.TestContext, globals.RPCTimeout) defer cancel() - cl.LatestPayloadBuilt, err = cl.NextBlockProducer.GetPayloadV1(ctx, cl.NextPayloadID) + if isShanghai(cl.LatestPayloadAttributes.Timestamp, cl.ShanghaiTimestamp) { + cl.LatestPayloadBuilt, err = cl.NextBlockProducer.GetPayloadV2(ctx, cl.NextPayloadID) + + } else { + cl.LatestPayloadBuilt, err = cl.NextBlockProducer.GetPayloadV1(ctx, cl.NextPayloadID) + } if err != nil { cl.Fatalf("CLMocker: Could not getPayload (%v, %v): %v", cl.NextBlockProducer.ID(), cl.NextPayloadID, err) } @@ -341,7 +372,11 @@ func (cl *CLMocker) broadcastNextNewPayload() { } func (cl *CLMocker) broadcastLatestForkchoice() { - for _, resp := range cl.BroadcastForkchoiceUpdated(&cl.LatestForkchoice, nil) { + version := 1 + if isShanghai(cl.LatestExecutedPayload.Timestamp, cl.ShanghaiTimestamp) { + version = 2 + } + for _, resp := range cl.BroadcastForkchoiceUpdated(&cl.LatestForkchoice, nil, version) { if resp.Error != nil { cl.Logf("CLMocker: BroadcastForkchoiceUpdated Error (%v): %v\n", resp.Container, resp.Error) } else if resp.ForkchoiceResponse.PayloadStatus.Status == api.VALID { @@ -385,12 +420,20 @@ func (cl *CLMocker) ProduceSingleBlock(callbacks BlockProcessCallbacks) { cl.pickNextPayloadProducer() + // Check if next withdrawals necessary, test can override this value on + // `OnPayloadProducerSelected` callback + if cl.NextWithdrawals == nil { + cl.SetNextWithdrawals(make(types.Withdrawals, 0)) + } + if callbacks.OnPayloadProducerSelected != nil { callbacks.OnPayloadProducerSelected() } cl.GetNextPayloadID() + cl.SetNextWithdrawals(nil) + if callbacks.OnGetPayloadID != nil { callbacks.OnGetPayloadID() } @@ -499,13 +542,21 @@ type ExecutePayloadOutcome struct { Error error } -func (cl *CLMocker) BroadcastNewPayload(payload *api.ExecutableDataV1) []ExecutePayloadOutcome { +func (cl *CLMocker) BroadcastNewPayload(payload *api.ExecutableData) []ExecutePayloadOutcome { responses := make([]ExecutePayloadOutcome, len(cl.EngineClients)) for i, ec := range cl.EngineClients { responses[i].Container = ec.ID() ctx, cancel := context.WithTimeout(cl.TestContext, globals.RPCTimeout) defer cancel() - execPayloadResp, err := ec.NewPayloadV1(ctx, payload) + var ( + execPayloadResp api.PayloadStatusV1 + err error + ) + if isShanghai(payload.Timestamp, cl.ShanghaiTimestamp) { + execPayloadResp, err = ec.NewPayloadV2(ctx, payload) + } else { + execPayloadResp, err = ec.NewPayloadV1(ctx, payload) + } if err != nil { cl.Errorf("CLMocker: Could not ExecutePayloadV1: %v", err) responses[i].Error = err @@ -523,7 +574,7 @@ type ForkChoiceOutcome struct { Error error } -func (cl *CLMocker) BroadcastForkchoiceUpdated(fcstate *api.ForkchoiceStateV1, payloadAttr *api.PayloadAttributesV1) []ForkChoiceOutcome { +func (cl *CLMocker) BroadcastForkchoiceUpdated(fcstate *api.ForkchoiceStateV1, payloadAttr *api.PayloadAttributes, version int) []ForkChoiceOutcome { responses := make([]ForkChoiceOutcome, len(cl.EngineClients)) for i, ec := range cl.EngineClients { responses[i].Container = ec.ID() @@ -531,7 +582,15 @@ func (cl *CLMocker) BroadcastForkchoiceUpdated(fcstate *api.ForkchoiceStateV1, p if cl.IsOptimisticallySyncing() || newPayloadStatus.Status == "VALID" { ctx, cancel := context.WithTimeout(cl.TestContext, globals.RPCTimeout) defer cancel() - fcUpdatedResp, err := ec.ForkchoiceUpdatedV1(ctx, fcstate, payloadAttr) + var ( + fcUpdatedResp api.ForkChoiceResponse + err error + ) + if version == 2 { + fcUpdatedResp, err = ec.ForkchoiceUpdatedV2(ctx, fcstate, payloadAttr) + } else if version == 1 { + fcUpdatedResp, err = ec.ForkchoiceUpdatedV1(ctx, fcstate, payloadAttr) + } if err != nil { cl.Errorf("CLMocker: Could not ForkchoiceUpdatedV1: %v", err) responses[i].Error = err diff --git a/simulators/ethereum/engine/go.mod b/simulators/ethereum/engine/go.mod index 252cb0a150..049cfbf31d 100644 --- a/simulators/ethereum/engine/go.mod +++ b/simulators/ethereum/engine/go.mod @@ -71,3 +71,5 @@ require ( golang.org/x/time v0.3.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect ) + +replace github.com/ethereum/go-ethereum => github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342 diff --git a/simulators/ethereum/engine/go.sum b/simulators/ethereum/engine/go.sum index 5e8d36d095..0b1f9a8f91 100644 --- a/simulators/ethereum/engine/go.sum +++ b/simulators/ethereum/engine/go.sum @@ -84,6 +84,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20220523130400-f11357ae11c7/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -244,6 +245,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw= +github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e/go.mod h1:j9cQbcqHQujT0oKJ38PylVfqohClLr3CvDC+Qcg+lhU= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o= diff --git a/simulators/ethereum/engine/helper/helper.go b/simulators/ethereum/engine/helper/helper.go index 7bafe69f8e..0707bad6bd 100644 --- a/simulators/ethereum/engine/helper/helper.go +++ b/simulators/ethereum/engine/helper/helper.go @@ -79,6 +79,7 @@ const ( InvalidTimestamp = "Timestamp" InvalidPrevRandao = "PrevRandao" InvalidOmmers = "Ommers" + InvalidWithdrawals = "Withdrawals" RemoveTransaction = "Incomplete Transactions" InvalidTransactionSignature = "Transaction Signature" InvalidTransactionNonce = "Transaction Nonce" @@ -89,7 +90,7 @@ const ( InvalidTransactionChainID = "Transaction ChainID" ) -func TransactionInPayload(payload *api.ExecutableDataV1, tx *types.Transaction) bool { +func TransactionInPayload(payload *api.ExecutableData, tx *types.Transaction) bool { for _, bytesTx := range payload.Transactions { var currentTx types.Transaction if err := currentTx.UnmarshalBinary(bytesTx); err == nil { diff --git a/simulators/ethereum/engine/helper/payload.go b/simulators/ethereum/engine/helper/payload.go index 1538512d89..110202bd4b 100644 --- a/simulators/ethereum/engine/helper/payload.go +++ b/simulators/ethereum/engine/helper/payload.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" api "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/hive/simulators/ethereum/engine/globals" ) @@ -28,11 +29,12 @@ type CustomPayloadData struct { BaseFeePerGas *big.Int BlockHash *common.Hash Transactions *[][]byte + Withdrawals *[]*types.Withdrawal } // Construct a customized payload by taking an existing payload as base and mixing it CustomPayloadData // BlockHash is calculated automatically. -func CustomizePayload(basePayload *api.ExecutableDataV1, customData *CustomPayloadData) (*api.ExecutableDataV1, error) { +func CustomizePayload(basePayload *api.ExecutableData, customData *CustomPayloadData) (*api.ExecutableData, error) { txs := basePayload.Transactions if customData.Transactions != nil { txs = *customData.Transactions @@ -61,6 +63,10 @@ func CustomizePayload(basePayload *api.ExecutableDataV1, customData *CustomPaylo Nonce: types.BlockNonce{0}, // could be overwritten BaseFee: basePayload.BaseFeePerGas, } + if basePayload.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(basePayload.Withdrawals), trie.NewStackTrie(nil)) + customPayloadHeader.WithdrawalsHash = &h + } // Overwrite custom information if customData.ParentHash != nil { @@ -99,9 +105,13 @@ func CustomizePayload(basePayload *api.ExecutableDataV1, customData *CustomPaylo if customData.BaseFeePerGas != nil { customPayloadHeader.BaseFee = customData.BaseFeePerGas } + if customData.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(*customData.Withdrawals), trie.NewStackTrie(nil)) + customPayloadHeader.WithdrawalsHash = &h + } // Return the new payload - return &api.ExecutableDataV1{ + return &api.ExecutableData{ ParentHash: customPayloadHeader.ParentHash, FeeRecipient: customPayloadHeader.Coinbase, StateRoot: customPayloadHeader.Root, @@ -160,12 +170,15 @@ func (customData *CustomPayloadData) String() string { if customData.Transactions != nil { customFieldsList = append(customFieldsList, fmt.Sprintf("Transactions=%v", customData.Transactions)) } + if customData.Withdrawals != nil { + customFieldsList = append(customFieldsList, fmt.Sprintf("Withdrawals=%v", customData.Withdrawals)) + } return strings.Join(customFieldsList, ", ") } // This function generates an invalid payload by taking a base payload and modifying the specified field such that it ends up being invalid. // One small consideration is that the payload needs to contain transactions and specially transactions using the PREVRANDAO opcode for all the fields to be compatible with this function. -func GenerateInvalidPayload(basePayload *api.ExecutableDataV1, payloadField InvalidPayloadBlockField) (*api.ExecutableDataV1, error) { +func GenerateInvalidPayload(basePayload *api.ExecutableData, payloadField InvalidPayloadBlockField) (*api.ExecutableData, error) { var customPayloadMod *CustomPayloadData switch payloadField { diff --git a/simulators/ethereum/engine/main.go b/simulators/ethereum/engine/main.go index 768b5fa558..ebf72c0849 100644 --- a/simulators/ethereum/engine/main.go +++ b/simulators/ethereum/engine/main.go @@ -14,6 +14,7 @@ import ( suite_engine "github.com/ethereum/hive/simulators/ethereum/engine/suites/engine" suite_sync "github.com/ethereum/hive/simulators/ethereum/engine/suites/sync" suite_transition "github.com/ethereum/hive/simulators/ethereum/engine/suites/transition" + suite_withdrawals "github.com/ethereum/hive/simulators/ethereum/engine/suites/withdrawals" ) func main() { @@ -40,6 +41,11 @@ func main() { Description: ` Test Engine API sync, pre/post merge.`[1:], } + withdrawals = hivesim.Suite{ + Name: "engine-withdrawals", + Description: ` + Test Engine API withdrawals, pre/post Shanghai.`[1:], + } ) simulator := hivesim.New() @@ -48,12 +54,14 @@ func main() { addTestsToSuite(&transition, suite_transition.Tests, "full") addTestsToSuite(&auth, suite_auth.Tests, "full") suite_sync.AddSyncTestsToSuite(simulator, &sync, suite_sync.Tests) + addTestsToSuite(&withdrawals, suite_withdrawals.Tests, "full") // Mark suites for execution hivesim.MustRunSuite(simulator, engine) hivesim.MustRunSuite(simulator, transition) hivesim.MustRunSuite(simulator, auth) hivesim.MustRunSuite(simulator, sync) + hivesim.MustRunSuite(simulator, withdrawals) } // Add test cases to a given test suite @@ -65,13 +73,20 @@ func addTestsToSuite(suite *hivesim.Suite, tests []test.Spec, nodeType string) { if currentTest.GenesisFile != "" { genesisPath = "./init/" + currentTest.GenesisFile } + // Load genesis for it to be modified before starting the client testFiles := hivesim.Params{"/genesis.json": genesisPath} // Calculate and set the TTD for this test ttd := helper.CalculateRealTTD(genesisPath, currentTest.TTD) + // Configure Forks newParams := globals.DefaultClientEnv.Set("HIVE_TERMINAL_TOTAL_DIFFICULTY", fmt.Sprintf("%d", ttd)) + if currentTest.ShanghaiTimestamp != nil { + newParams = newParams.Set("HIVE_SHANGHAI_TIMESTAMP", fmt.Sprintf("%d", currentTest.ShanghaiTimestamp)) + } + if nodeType != "" { newParams = newParams.Set("HIVE_NODETYPE", nodeType) } + if currentTest.ChainFile != "" { // We are using a Proof of Work chain file, remove all clique-related settings // TODO: Nethermind still requires HIVE_MINER for the Engine API @@ -100,7 +115,7 @@ func addTestsToSuite(suite *hivesim.Suite, tests []test.Spec, nodeType string) { timeout = time.Second * time.Duration(currentTest.TimeoutSeconds) } // Run the test case - test.Run(currentTest.Name, big.NewInt(ttd), currentTest.SlotsToSafe, currentTest.SlotsToFinalized, timeout, t, c, currentTest.Run, newParams, testFiles, currentTest.TestTransactionType, currentTest.SafeSlotsToImportOptimistically) + test.Run(currentTest, big.NewInt(ttd), timeout, t, c, newParams, testFiles) }, }) } diff --git a/simulators/ethereum/engine/suites/engine/tests.go b/simulators/ethereum/engine/suites/engine/tests.go index 6b0da6ce44..d71d43c465 100644 --- a/simulators/ethereum/engine/suites/engine/tests.go +++ b/simulators/ethereum/engine/suites/engine/tests.go @@ -1232,7 +1232,7 @@ func invalidTerminalBlockNewPayload(t *test.Env) { gblock := helper.LoadGenesisBlock(t.ClientFiles["/genesis.json"]) // Create a dummy payload to send in the NewPayload call - payload := api.ExecutableDataV1{ + payload := api.ExecutableData{ ParentHash: gblock.Hash(), FeeRecipient: common.Address{}, StateRoot: gblock.Root(), @@ -1326,7 +1326,7 @@ func unknownFinalizedBlockHash(t *test.Env) { // Test again using PayloadAttributes, should also return INVALID and no PayloadID r = t.TestEngine.TestEngineForkchoiceUpdatedV1(&forkchoiceStateUnknownFinalizedHash, - &api.PayloadAttributesV1{ + &api.PayloadAttributes{ Timestamp: t.CLMock.LatestExecutedPayload.Timestamp + 1, Random: common.Hash{}, SuggestedFeeRecipient: common.Address{}, @@ -1367,7 +1367,7 @@ func unknownHeadBlockHash(t *test.Env) { // Test again using PayloadAttributes, should also return SYNCING and no PayloadID r = t.TestEngine.TestEngineForkchoiceUpdatedV1(&forkchoiceStateUnknownHeadHash, - &api.PayloadAttributesV1{ + &api.PayloadAttributes{ Timestamp: t.CLMock.LatestExecutedPayload.Timestamp + 1, Random: common.Hash{}, SuggestedFeeRecipient: common.Address{}, @@ -1383,8 +1383,8 @@ func inconsistentForkchoiceStateGen(inconsistency string) func(t *test.Env) { // Wait until TTD is reached by this client t.CLMock.WaitForTTD() - canonicalPayloads := make([]*api.ExecutableDataV1, 0) - alternativePayloads := make([]*api.ExecutableDataV1, 0) + canonicalPayloads := make([]*api.ExecutableData, 0) + alternativePayloads := make([]*api.ExecutableData, 0) // Produce blocks before starting the test t.CLMock.ProduceBlocks(3, clmock.BlockProcessCallbacks{ OnGetPayload: func() { @@ -1458,7 +1458,7 @@ func invalidPayloadAttributesGen(syncing bool) func(*test.Env) { SafeBlockHash: t.CLMock.LatestForkchoice.SafeBlockHash, FinalizedBlockHash: t.CLMock.LatestForkchoice.FinalizedBlockHash, } - attr := api.PayloadAttributesV1{ + attr := api.PayloadAttributes{ Timestamp: 0, Random: common.Hash{}, SuggestedFeeRecipient: common.Address{}, @@ -1547,7 +1547,7 @@ func badHashOnNewPayloadGen(syncing bool, sidechain bool) func(*test.Env) { t.CLMock.ProduceBlocks(5, clmock.BlockProcessCallbacks{}) var ( - alteredPayload api.ExecutableDataV1 + alteredPayload api.ExecutableData invalidPayloadHash common.Hash ) @@ -1737,7 +1737,7 @@ func invalidPayloadTestCaseGen(payloadField helper.InvalidPayloadBlockField, syn } var ( - alteredPayload *api.ExecutableDataV1 + alteredPayload *api.ExecutableData err error ) @@ -1783,7 +1783,7 @@ func invalidPayloadTestCaseGen(payloadField helper.InvalidPayloadBlockField, syn SafeBlockHash: alteredPayload.BlockHash, FinalizedBlockHash: alteredPayload.BlockHash, } - payloadAttrbutes := api.PayloadAttributesV1{ + payloadAttrbutes := api.PayloadAttributes{ Timestamp: alteredPayload.Timestamp + 1, Random: common.Hash{}, SuggestedFeeRecipient: common.Address{}, @@ -1918,7 +1918,7 @@ func invalidMissingAncestorReOrgGen(invalid_index int, payloadField helper.Inval n := 10 // Slice to save the side B chain - altChainPayloads := make([]*api.ExecutableDataV1, 0) + altChainPayloads := make([]*api.ExecutableData, 0) // Append the common ancestor altChainPayloads = append(altChainPayloads, &cA) @@ -1942,7 +1942,7 @@ func invalidMissingAncestorReOrgGen(invalid_index int, payloadField helper.Inval }, OnGetPayload: func() { var ( - sidePayload *api.ExecutableDataV1 + sidePayload *api.ExecutableData err error ) // Insert extraData to ensure we deviate from the main payload, which contains empty extradata @@ -2144,7 +2144,7 @@ func (spec InvalidMissingAncestorReOrgSpec) GenerateSync() func(*test.Env) { }, OnGetPayload: func() { var ( - sidePayload *api.ExecutableDataV1 + sidePayload *api.ExecutableData err error ) // Insert extraData to ensure we deviate from the main payload, which contains empty extradata @@ -2190,7 +2190,7 @@ func (spec InvalidMissingAncestorReOrgSpec) GenerateSync() func(*test.Env) { } } } - // Invalidate fields not available in the ExecutableDataV1 + // Invalidate fields not available in the ExecutableData sideBlock, err = helper.GenerateInvalidPayloadBlock(sideBlock, uncle, spec.PayloadField) if err != nil { t.Fatalf("FAIL (%s): Unable to customize payload block: %v", t.TestName, err) @@ -2544,7 +2544,7 @@ func blockStatusReorg(t *test.Env) { HeadBlockHash: customizedPayload.BlockHash, SafeBlockHash: t.CLMock.LatestForkchoice.SafeBlockHash, FinalizedBlockHash: t.CLMock.LatestForkchoice.FinalizedBlockHash, - }, nil) + }, nil, 1) // Verify the client is serving the latest HeadBlock r := t.TestEngine.TestHeaderByNumber(Head) @@ -2604,7 +2604,7 @@ func reorgPrevValidatedPayloadOnSideChain(t *test.Env) { t.CLMock.ProduceBlocks(5, clmock.BlockProcessCallbacks{}) var ( - sidechainPayloads = make([]*api.ExecutableDataV1, 0) + sidechainPayloads = make([]*api.ExecutableData, 0) sidechainPayloadCount = 5 ) @@ -2641,7 +2641,7 @@ func reorgPrevValidatedPayloadOnSideChain(t *test.Env) { HeadBlockHash: sidechainPayloads[len(sidechainPayloads)-2].BlockHash, SafeBlockHash: t.CLMock.LatestForkchoice.SafeBlockHash, FinalizedBlockHash: t.CLMock.LatestForkchoice.FinalizedBlockHash, - }, &api.PayloadAttributesV1{ + }, &api.PayloadAttributes{ Timestamp: t.CLMock.LatestHeader.Time, Random: prevRandao, SuggestedFeeRecipient: common.Address{}, @@ -2668,7 +2668,7 @@ func safeReorgToSideChain(t *test.Env) { t.CLMock.WaitForTTD() // Produce an alternative chain - sidechainPayloads := make([]*api.ExecutableDataV1, 0) + sidechainPayloads := make([]*api.ExecutableData, 0) // Produce three payloads `P1`, `P2`, `P3`, along with the side chain payloads `P2'`, `P3'` // First payload is finalized so no alternative payload @@ -2735,7 +2735,7 @@ func reorgBackFromSyncing(t *test.Env) { t.CLMock.WaitForTTD() // Produce an alternative chain - sidechainPayloads := make([]*api.ExecutableDataV1, 0) + sidechainPayloads := make([]*api.ExecutableData, 0) t.CLMock.ProduceBlocks(10, clmock.BlockProcessCallbacks{ OnGetPayload: func() { // Generate an alternative payload by simply adding extraData to the block @@ -2795,7 +2795,7 @@ func transactionReorg(t *test.Env) { for i := 0; i < txCount; i++ { var ( - noTxnPayload api.ExecutableDataV1 + noTxnPayload api.ExecutableData tx *types.Transaction ) // Generate two payloads, one with the transaction and the other one without it @@ -2873,7 +2873,7 @@ func transactionReorg(t *test.Env) { } // Re-org back - t.CLMock.BroadcastForkchoiceUpdated(&t.CLMock.LatestForkchoice, nil) + t.CLMock.BroadcastForkchoiceUpdated(&t.CLMock.LatestForkchoice, nil, 1) }, }) @@ -2898,8 +2898,8 @@ func transactionReorgBlockhash(newNPOnRevert bool) func(t *test.Env) { for i := 0; i < txCount; i++ { var ( - mainPayload *api.ExecutableDataV1 - sidePayload *api.ExecutableDataV1 + mainPayload *api.ExecutableData + sidePayload *api.ExecutableData tx *types.Transaction ) @@ -2986,7 +2986,7 @@ func transactionReorgBlockhash(newNPOnRevert bool) func(t *test.Env) { HeadBlockHash: mainPayload.BlockHash, SafeBlockHash: t.CLMock.LatestForkchoice.SafeBlockHash, FinalizedBlockHash: t.CLMock.LatestForkchoice.FinalizedBlockHash, - }, nil) + }, nil, 1) // Not it should be back with main payload txt = t.TestEngine.TestTransactionReceipt(tx.Hash()) @@ -3024,7 +3024,7 @@ func sidechainReorg(t *test.Env) { rand.Read(alternativePrevRandao[:]) r := t.TestEngine.TestEngineForkchoiceUpdatedV1(&t.CLMock.LatestForkchoice, - &api.PayloadAttributesV1{ + &api.PayloadAttributes{ Timestamp: t.CLMock.LatestHeader.Time + 1, Random: alternativePrevRandao, SuggestedFeeRecipient: t.CLMock.NextFeeRecipient, @@ -3215,7 +3215,7 @@ func inOrderPayloads(t *test.Env) { func validPayloadFcUSyncingClient(t *test.Env) { var ( secondaryClient client.EngineClient - previousPayload api.ExecutableDataV1 + previousPayload api.ExecutableData ) { // To allow sending the primary engine client into SYNCING state, we need a secondary client to guide the payload creation @@ -3257,7 +3257,7 @@ func validPayloadFcUSyncingClient(t *test.Env) { HeadBlockHash: t.CLMock.LatestPayloadBuilt.BlockHash, SafeBlockHash: t.CLMock.LatestPayloadBuilt.BlockHash, FinalizedBlockHash: t.CLMock.LatestPayloadBuilt.BlockHash, - }, &api.PayloadAttributesV1{ + }, &api.PayloadAttributes{ Timestamp: t.CLMock.LatestPayloadBuilt.Timestamp + 1, Random: common.Hash{}, SuggestedFeeRecipient: common.Address{}, @@ -3368,11 +3368,11 @@ func payloadBuildAfterNewInvalidPayload(t *test.Env) { if t.CLMock.NextBlockProducer == invalidPayloadProducer.Engine { invalidPayloadProducer = secondaryEngineTest } - var inv_p *api.ExecutableDataV1 + var inv_p *api.ExecutableData { // Get a payload from the invalid payload producer and invalidate it - r := invalidPayloadProducer.TestEngineForkchoiceUpdatedV1(&t.CLMock.LatestForkchoice, &api.PayloadAttributesV1{ + r := invalidPayloadProducer.TestEngineForkchoiceUpdatedV1(&t.CLMock.LatestForkchoice, &api.PayloadAttributes{ Timestamp: t.CLMock.LatestHeader.Time + 1, Random: common.Hash{}, SuggestedFeeRecipient: common.Address{}, diff --git a/simulators/ethereum/engine/suites/sync/tests.go b/simulators/ethereum/engine/suites/sync/tests.go index f15e849395..29f0537994 100644 --- a/simulators/ethereum/engine/suites/sync/tests.go +++ b/simulators/ethereum/engine/suites/sync/tests.go @@ -98,7 +98,7 @@ func AddSyncTestsToSuite(sim *hivesim.Simulation, suite *hivesim.Suite, tests [] } // Run the test case - test.Run(currentTest.Name, big.NewInt(ttd), currentTest.SlotsToSafe, currentTest.SlotsToFinalized, timeout, t, c, currentTest.Run, syncClientParams, testFiles.Copy(), currentTest.TestTransactionType, currentTest.SafeSlotsToImportOptimistically) + test.Run(currentTest, big.NewInt(ttd), timeout, t, c, syncClientParams, testFiles.Copy()) }, }) } diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go new file mode 100644 index 0000000000..682d0cc051 --- /dev/null +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -0,0 +1,53 @@ +package suite_withdrawals + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/hive/simulators/ethereum/engine/clmock" + "github.com/ethereum/hive/simulators/ethereum/engine/test" +) + +var ( + big0 = new(big.Int) + big1 = big.NewInt(1) + Head *big.Int // Nil + Pending = big.NewInt(-2) + Finalized = big.NewInt(-3) + Safe = big.NewInt(-4) +) + +// Execution specification reference: +// https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md + +var Tests = []test.Spec{ + { + Name: "Sanity Withdrawal Test", + Run: sanityWithdrawalTest, + TTD: 0, + ShanghaiTimestamp: big.NewInt(0), + }, +} + +// Make a simple withdrawal and verify balance change on FCU. +func sanityWithdrawalTest(t *test.Env) { + t.CLMock.WaitForTTD() + withdrawalAddress := common.HexToAddress("0xAA") + withdrawalAmount := big.NewInt(100) + t.CLMock.ProduceSingleBlock(clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + withdrawals := make(types.Withdrawals, 0) + withdrawals = append(withdrawals, &types.Withdrawal{ + Index: 0, + Validator: 0, + Address: withdrawalAddress, + Amount: withdrawalAmount, + }) + t.CLMock.SetNextWithdrawals(withdrawals) + }, + }) + + r := t.TestEngine.TestBalanceAt(withdrawalAddress, nil) + r.ExpectBalanceEqual(withdrawalAmount) +} diff --git a/simulators/ethereum/engine/test/env.go b/simulators/ethereum/engine/test/env.go index de4251f532..311dfb13dd 100644 --- a/simulators/ethereum/engine/test/env.go +++ b/simulators/ethereum/engine/test/env.go @@ -45,9 +45,9 @@ type Env struct { TestTransactionType helper.TestTransactionType } -func Run(testName string, ttd *big.Int, slotsToSafe *big.Int, slotsToFinalized *big.Int, timeout time.Duration, t *hivesim.T, c *hivesim.Client, fn func(*Env), cParams hivesim.Params, cFiles hivesim.Params, testTransactionType helper.TestTransactionType, safeSlotsToImportOptimistically int64) { +func Run(testSpec Spec, ttd *big.Int, timeout time.Duration, t *hivesim.T, c *hivesim.Client, cParams hivesim.Params, cFiles hivesim.Params) { // Setup the CL Mocker for this test - clMocker := clmock.NewCLMocker(t, slotsToSafe, slotsToFinalized, big.NewInt(safeSlotsToImportOptimistically)) + clMocker := clmock.NewCLMocker(t, testSpec.SlotsToSafe, testSpec.SlotsToFinalized, big.NewInt(testSpec.SafeSlotsToImportOptimistically), testSpec.ShanghaiTimestamp) // Defer closing all clients defer func() { clMocker.CloseClients() @@ -66,7 +66,7 @@ func Run(testName string, ttd *big.Int, slotsToSafe *big.Int, slotsToFinalized * env := &Env{ T: t, - TestName: testName, + TestName: testSpec.Name, Client: c, Engine: ec, Eth: ec, @@ -74,7 +74,7 @@ func Run(testName string, ttd *big.Int, slotsToSafe *big.Int, slotsToFinalized * CLMock: clMocker, ClientParams: cParams, ClientFiles: cFiles, - TestTransactionType: testTransactionType, + TestTransactionType: testSpec.TestTransactionType, } // Before running the test, make sure Eth and Engine ports are open for the client @@ -106,7 +106,7 @@ func Run(testName string, ttd *big.Int, slotsToSafe *big.Int, slotsToFinalized * }() // Run the test - fn(env) + testSpec.Run(env) } func (t *Env) MainTTD() *big.Int { diff --git a/simulators/ethereum/engine/test/expect.go b/simulators/ethereum/engine/test/expect.go index ebfa4d75a8..39b0e13f11 100644 --- a/simulators/ethereum/engine/test/expect.go +++ b/simulators/ethereum/engine/test/expect.go @@ -79,7 +79,7 @@ type ForkchoiceResponseExpectObject struct { Error error } -func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV1(fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributesV1) *ForkchoiceResponseExpectObject { +func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV1(fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) *ForkchoiceResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() resp, err := tec.Engine.ForkchoiceUpdatedV1(ctx, fcState, pAttributes) @@ -90,6 +90,17 @@ func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV1(fcState *api.Forkchoi } } +func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV2(fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) *ForkchoiceResponseExpectObject { + ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) + defer cancel() + resp, err := tec.Engine.ForkchoiceUpdatedV2(ctx, fcState, pAttributes) + return &ForkchoiceResponseExpectObject{ + ExpectEnv: &ExpectEnv{tec.Env}, + Response: resp, + Error: err, + } +} + func (exp *ForkchoiceResponseExpectObject) ExpectNoError() { if exp.Error != nil { exp.Fatalf("FAIL (%s): Unexpected error on EngineForkchoiceUpdatedV1: %v, expected=", exp.TestName, exp.Error) @@ -156,7 +167,7 @@ type NewPayloadResponseExpectObject struct { Error error } -func (tec *TestEngineClient) TestEngineNewPayloadV1(payload *api.ExecutableDataV1) *NewPayloadResponseExpectObject { +func (tec *TestEngineClient) TestEngineNewPayloadV1(payload *api.ExecutableData) *NewPayloadResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() status, err := tec.Engine.NewPayloadV1(ctx, payload) @@ -167,6 +178,17 @@ func (tec *TestEngineClient) TestEngineNewPayloadV1(payload *api.ExecutableDataV } } +func (tec *TestEngineClient) TestEngineNewPayloadV2(payload *api.ExecutableData) *NewPayloadResponseExpectObject { + ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) + defer cancel() + status, err := tec.Engine.NewPayloadV2(ctx, payload) + return &NewPayloadResponseExpectObject{ + ExpectEnv: &ExpectEnv{tec.Env}, + Status: status, + Error: err, + } +} + func (exp *NewPayloadResponseExpectObject) ExpectNoError() { if exp.Error != nil { exp.Fatalf("FAIL (%s): Expected no error on EngineNewPayloadV1: error=%v", exp.TestName, exp.Error) @@ -221,7 +243,7 @@ func (exp *NewPayloadResponseExpectObject) ExpectLatestValidHash(lvh *common.Has // GetPayloadV1 type GetPayloadResponseExpectObject struct { *ExpectEnv - Payload api.ExecutableDataV1 + Payload api.ExecutableData Error error } @@ -236,6 +258,17 @@ func (tec *TestEngineClient) TestEngineGetPayloadV1(payloadID *api.PayloadID) *G } } +func (tec *TestEngineClient) TestEngineGetPayloadV2(payloadID *api.PayloadID) *GetPayloadResponseExpectObject { + ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) + defer cancel() + payload, err := tec.Engine.GetPayloadV2(ctx, payloadID) + return &GetPayloadResponseExpectObject{ + ExpectEnv: &ExpectEnv{tec.Env}, + Payload: payload, + Error: err, + } +} + func (exp *GetPayloadResponseExpectObject) ExpectNoError() { if exp.Error != nil { exp.Fatalf("FAIL (%s): Expected no error on EngineGetPayloadV1: error=%v", exp.TestName, exp.Error) diff --git a/simulators/ethereum/engine/test/spec.go b/simulators/ethereum/engine/test/spec.go index af5583ba08..046c53e1c1 100644 --- a/simulators/ethereum/engine/test/spec.go +++ b/simulators/ethereum/engine/test/spec.go @@ -21,6 +21,9 @@ type Spec struct { // Default: 0 TTD int64 + // Shanghai Timestamp + ShanghaiTimestamp *big.Int + // CL Mocker configuration for slots to `safe` and `finalized` respectively SlotsToSafe *big.Int SlotsToFinalized *big.Int From cf02f1ae6ca9cb6422006c01dbe3487edd95498c Mon Sep 17 00:00:00 2001 From: marioevz Date: Sat, 19 Nov 2022 11:43:05 -0600 Subject: [PATCH 03/49] clients/nethermind: Use withdrawals branch --- clients/nethermind/Dockerfile | 2 +- clients/nethermind/mapper.jq | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/clients/nethermind/Dockerfile b/clients/nethermind/Dockerfile index 98551f2617..ed891f8533 100644 --- a/clients/nethermind/Dockerfile +++ b/clients/nethermind/Dockerfile @@ -1,5 +1,5 @@ ARG branch=latest -FROM nethermindeth/hive:$branch +FROM nethermindeth/nethermind:withdrawals_yolo RUN apt-get update && apt-get install -y wget RUN wget https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -O /usr/local/bin/jq && \ diff --git a/clients/nethermind/mapper.jq b/clients/nethermind/mapper.jq index 937d64c43f..4340fd6f1d 100644 --- a/clients/nethermind/mapper.jq +++ b/clients/nethermind/mapper.jq @@ -136,6 +136,9 @@ def clique_engine: # Merge "MergeForkIdTransition": env.HIVE_MERGE_BLOCK_ID|to_hex, + # Shanghai + "eip4895TransitionTimestamp": env.HIVE_SHANGHAI_TIMESTAMP|to_hex, + # Other chain parameters "networkID": env.HIVE_NETWORK_ID|to_hex, "chainID": env.HIVE_CHAIN_ID|to_hex, From e9c6bb55324fc3a37ff5cbe6f59d1cd4d3f384fc Mon Sep 17 00:00:00 2001 From: marioevz Date: Sat, 19 Nov 2022 11:44:40 -0600 Subject: [PATCH 04/49] simulators/ethereum/engine: CLMock changes --- simulators/ethereum/engine/clmock/clmock.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/simulators/ethereum/engine/clmock/clmock.go b/simulators/ethereum/engine/clmock/clmock.go index e0ffdb3859..ccefd577bb 100644 --- a/simulators/ethereum/engine/clmock/clmock.go +++ b/simulators/ethereum/engine/clmock/clmock.go @@ -2,6 +2,7 @@ package clmock import ( "context" + "encoding/json" "fmt" "math/big" "math/rand" @@ -180,6 +181,8 @@ func (cl *CLMocker) SetTTDBlockClient(ec client.EngineClient) { cl.Fatalf("CLMocker: Attempted to set TTD Block when TTD had not been reached: %d > %d", ec.TerminalTotalDifficulty(), ttd) } else { cl.Logf("CLMocker: TTD has been reached at block %d (%d>=%d)\n", cl.LatestHeader.Number, ttd, ec.TerminalTotalDifficulty()) + jsH, _ := json.MarshalIndent(cl.LatestHeader, "", " ") + cl.Logf("CLMocker: Block %d: %s\n", cl.LatestHeader.Number, jsH) } cl.TTDReached = true @@ -257,7 +260,7 @@ func (cl *CLMocker) SetNextWithdrawals(nextWithdrawals types.Withdrawals) { cl.NextWithdrawals = nextWithdrawals } -func (cl *CLMocker) GetNextPayloadID() { +func (cl *CLMocker) RequestNextPayload() { // Generate a random value for the PrevRandao field nextPrevRandao := common.Hash{} rand.Read(nextPrevRandao[:]) @@ -284,17 +287,20 @@ func (cl *CLMocker) GetNextPayloadID() { ctx, cancel := context.WithTimeout(cl.TestContext, globals.RPCTimeout) defer cancel() var ( - resp api.ForkChoiceResponse - err error + resp api.ForkChoiceResponse + fcUVersion int + err error ) if isShanghai(cl.LatestPayloadAttributes.Timestamp, cl.ShanghaiTimestamp) { + fcUVersion = 2 resp, err = cl.NextBlockProducer.ForkchoiceUpdatedV2(ctx, &cl.LatestForkchoice, &cl.LatestPayloadAttributes) } else { + fcUVersion = 1 resp, err = cl.NextBlockProducer.ForkchoiceUpdatedV1(ctx, &cl.LatestForkchoice, &cl.LatestPayloadAttributes) } if err != nil { - cl.Fatalf("CLMocker: Could not send forkchoiceUpdatedV1 (%v): %v", cl.NextBlockProducer.ID(), err) + cl.Fatalf("CLMocker: Could not send forkchoiceUpdatedV%d (%v): %v", fcUVersion, cl.NextBlockProducer.ID(), err) } if resp.PayloadStatus.Status != api.VALID { cl.Fatalf("CLMocker: Unexpected forkchoiceUpdated Response from Payload builder: %v", resp) @@ -430,7 +436,7 @@ func (cl *CLMocker) ProduceSingleBlock(callbacks BlockProcessCallbacks) { callbacks.OnPayloadProducerSelected() } - cl.GetNextPayloadID() + cl.RequestNextPayload() cl.SetNextWithdrawals(nil) From 06754b1efd59af7da099454d894cb38de8ad9c23 Mon Sep 17 00:00:00 2001 From: marioevz Date: Sat, 19 Nov 2022 11:45:08 -0600 Subject: [PATCH 05/49] simulators/ethereum/engine: Add timestamp global --- simulators/ethereum/engine/globals/globals.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/simulators/ethereum/engine/globals/globals.go b/simulators/ethereum/engine/globals/globals.go index efd65ceea6..6f205b567f 100644 --- a/simulators/ethereum/engine/globals/globals.go +++ b/simulators/ethereum/engine/globals/globals.go @@ -13,10 +13,11 @@ import ( var ( // Test chain parameters - ChainID = big.NewInt(7) - GasPrice = big.NewInt(30 * params.GWei) - GasTipPrice = big.NewInt(1 * params.GWei) - NetworkID = big.NewInt(7) + ChainID = big.NewInt(7) + GasPrice = big.NewInt(30 * params.GWei) + GasTipPrice = big.NewInt(1 * params.GWei) + NetworkID = big.NewInt(7) + GenesisTimestamp = int64(0x1234) // RPC Timeout for every call RPCTimeout = 10 * time.Second From 43ef1491f73df0d17b18362cf291d9415e48eb91 Mon Sep 17 00:00:00 2001 From: marioevz Date: Sat, 19 Nov 2022 11:46:18 -0600 Subject: [PATCH 06/49] simulators/ethereum/engine: Withdrawals required changes --- .../engine/client/hive_rpc/hive_rpc.go | 28 +++++----- simulators/ethereum/engine/helper/helper.go | 47 ++++++++-------- simulators/ethereum/engine/helper/payload.go | 22 ++++---- simulators/ethereum/engine/test/expect.go | 53 ++++++++++++------- simulators/ethereum/engine/test/spec.go | 7 +-- 5 files changed, 90 insertions(+), 67 deletions(-) diff --git a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go index abe2b5ccab..a963dfa3f0 100644 --- a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go +++ b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go @@ -45,10 +45,10 @@ func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Cont if clientType == "" { cs, err := T.Sim.ClientTypes() if err != nil { - return nil, fmt.Errorf("Client type was not supplied and simulator returned error on trying to get all client types: %v", err) + return nil, fmt.Errorf("client type was not supplied and simulator returned error on trying to get all client types: %v", err) } - if cs == nil || len(cs) == 0 { - return nil, fmt.Errorf("Client type was not supplied and simulator returned empty client types: %v", cs) + if len(cs) == 0 { + return nil, fmt.Errorf("client type was not supplied and simulator returned empty client types: %v", cs) } clientType = cs[0].Name } @@ -65,14 +65,14 @@ func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Cont ClientFiles = ClientFiles.Set("/chain.rlp", "./chains/"+s.ChainFile) } if _, ok := ClientFiles["/genesis.json"]; !ok { - return nil, fmt.Errorf("Cannot start without genesis file") + return nil, fmt.Errorf("cannot start without genesis file") } if ttd == nil { if ttdStr, ok := ClientParams["HIVE_TERMINAL_TOTAL_DIFFICULTY"]; ok { // Retrieve TTD from parameters ttd, ok = new(big.Int).SetString(ttdStr, 10) if !ok { - return nil, fmt.Errorf("Unable to parse TTD from parameters") + return nil, fmt.Errorf("unable to parse TTD from parameters") } } } else { @@ -81,7 +81,7 @@ func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Cont ClientParams = ClientParams.Set("HIVE_TERMINAL_TOTAL_DIFFICULTY", fmt.Sprintf("%d", ttdInt)) ttd = big.NewInt(ttdInt) } - if bootClients != nil && len(bootClients) > 0 { + if len(bootClients) > 0 { var ( enodes = make([]string, len(bootClients)) err error @@ -89,7 +89,7 @@ func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Cont for i, bootClient := range bootClients { enodes[i], err = bootClient.EnodeURL() if err != nil { - return nil, fmt.Errorf("Unable to obtain bootnode: %v", err) + return nil, fmt.Errorf("unable to obtain bootnode: %v", err) } } enodeString := strings.Join(enodes, ",") @@ -164,17 +164,17 @@ type HiveRPCEngineClient struct { // NewClient creates a engine client that uses the given RPC client. func NewHiveRPCEngineClient(h *hivesim.Client, enginePort int, ethPort int, jwtSecretBytes []byte, ttd *big.Int, transport http.RoundTripper) *HiveRPCEngineClient { - client := &http.Client{ - Transport: transport, - } // Prepare HTTP Client - rpcHttpClient, _ := rpc.DialHTTPWithClient(fmt.Sprintf("http://%s:%d/", h.IP, enginePort), client) + rpcHttpClient, err := rpc.DialHTTPWithClient(fmt.Sprintf("http://%s:%d/", h.IP, enginePort), &http.Client{Transport: transport}) + if err != nil { + panic(err) + } // Prepare ETH Client - client = &http.Client{ - Transport: transport, + rpcClient, err := rpc.DialHTTPWithClient(fmt.Sprintf("http://%s:%d/", h.IP, ethPort), &http.Client{Transport: transport}) + if err != nil { + panic(err) } - rpcClient, _ := rpc.DialHTTPWithClient(fmt.Sprintf("http://%s:%d/", h.IP, ethPort), client) eth := ethclient.NewClient(rpcClient) return &HiveRPCEngineClient{ h: h, diff --git a/simulators/ethereum/engine/helper/helper.go b/simulators/ethereum/engine/helper/helper.go index 0707bad6bd..e760a2c969 100644 --- a/simulators/ethereum/engine/helper/helper.go +++ b/simulators/ethereum/engine/helper/helper.go @@ -70,24 +70,24 @@ func (rt *LoggingRoundTrip) RoundTrip(req *http.Request) (*http.Response, error) type InvalidPayloadBlockField string const ( - InvalidParentHash InvalidPayloadBlockField = "ParentHash" - InvalidStateRoot = "StateRoot" - InvalidReceiptsRoot = "ReceiptsRoot" - InvalidNumber = "Number" - InvalidGasLimit = "GasLimit" - InvalidGasUsed = "GasUsed" - InvalidTimestamp = "Timestamp" - InvalidPrevRandao = "PrevRandao" - InvalidOmmers = "Ommers" - InvalidWithdrawals = "Withdrawals" - RemoveTransaction = "Incomplete Transactions" - InvalidTransactionSignature = "Transaction Signature" - InvalidTransactionNonce = "Transaction Nonce" - InvalidTransactionGas = "Transaction Gas" - InvalidTransactionGasPrice = "Transaction GasPrice" - InvalidTransactionGasTipPrice = "Transaction GasTipCapPrice" - InvalidTransactionValue = "Transaction Value" - InvalidTransactionChainID = "Transaction ChainID" + InvalidParentHash = "ParentHash" + InvalidStateRoot = "StateRoot" + InvalidReceiptsRoot = "ReceiptsRoot" + InvalidNumber = "Number" + InvalidGasLimit = "GasLimit" + InvalidGasUsed = "GasUsed" + InvalidTimestamp = "Timestamp" + InvalidPrevRandao = "PrevRandao" + InvalidOmmers = "Ommers" + InvalidWithdrawals = "Withdrawals" + RemoveTransaction = "Incomplete Transactions" + InvalidTransactionSignature = "Transaction Signature" + InvalidTransactionNonce = "Transaction Nonce" + InvalidTransactionGas = "Transaction Gas" + InvalidTransactionGasPrice = "Transaction GasPrice" + InvalidTransactionGasTipPrice = "Transaction GasTipCapPrice" + InvalidTransactionValue = "Transaction Value" + InvalidTransactionChainID = "Transaction ChainID" ) func TransactionInPayload(payload *api.ExecutableData, tx *types.Transaction) bool { @@ -145,16 +145,16 @@ func gethDebugPrevRandaoTransaction(ctx context.Context, c *rpc.Client, tx *type for i, l := range er.StructLogs { if l.Op == "DIFFICULTY" || l.Op == "PREVRANDAO" { if i+1 >= len(er.StructLogs) { - return errors.New(fmt.Sprintf("No information after PREVRANDAO operation")) + return errors.New("no information after PREVRANDAO operation") } prevRandaoFound = true stack := *(er.StructLogs[i+1].Stack) if len(stack) < 1 { - return errors.New(fmt.Sprintf("Invalid stack after PREVRANDAO operation: %v", l.Stack)) + return fmt.Errorf("invalid stack after PREVRANDAO operation: %v", l.Stack) } stackHash := common.HexToHash(stack[0]) if stackHash != *expectedPrevRandao { - return errors.New(fmt.Sprintf("Invalid stack after PREVRANDAO operation, %v != %v", stackHash, expectedPrevRandao)) + return fmt.Errorf("invalid stack after PREVRANDAO operation, %v != %v", stackHash, expectedPrevRandao) } } } @@ -286,7 +286,7 @@ func WaitForTTDWithTimeout(ec client.EngineClient, ctx context.Context) error { return nil } case <-ctx.Done(): - return fmt.Errorf("Timeout reached") + return fmt.Errorf("timeout reached") } } } @@ -386,6 +386,9 @@ func SendNextTransaction(testCtx context.Context, node client.EngineClient, reci return nil, err } tx, err := MakeTransaction(nonce, &recipient, 75000, amount, payload, txType) + if err != nil { + return nil, err + } for { ctx, cancel := context.WithTimeout(testCtx, globals.RPCTimeout) defer cancel() diff --git a/simulators/ethereum/engine/helper/payload.go b/simulators/ethereum/engine/helper/payload.go index 110202bd4b..7ee1e1dd5c 100644 --- a/simulators/ethereum/engine/helper/payload.go +++ b/simulators/ethereum/engine/helper/payload.go @@ -29,7 +29,7 @@ type CustomPayloadData struct { BaseFeePerGas *big.Int BlockHash *common.Hash Transactions *[][]byte - Withdrawals *[]*types.Withdrawal + Withdrawals types.Withdrawals } // Construct a customized payload by taking an existing payload as base and mixing it CustomPayloadData @@ -63,11 +63,6 @@ func CustomizePayload(basePayload *api.ExecutableData, customData *CustomPayload Nonce: types.BlockNonce{0}, // could be overwritten BaseFee: basePayload.BaseFeePerGas, } - if basePayload.Withdrawals != nil { - h := types.DeriveSha(types.Withdrawals(basePayload.Withdrawals), trie.NewStackTrie(nil)) - customPayloadHeader.WithdrawalsHash = &h - } - // Overwrite custom information if customData.ParentHash != nil { customPayloadHeader.ParentHash = *customData.ParentHash @@ -106,12 +101,15 @@ func CustomizePayload(basePayload *api.ExecutableData, customData *CustomPayload customPayloadHeader.BaseFee = customData.BaseFeePerGas } if customData.Withdrawals != nil { - h := types.DeriveSha(types.Withdrawals(*customData.Withdrawals), trie.NewStackTrie(nil)) + h := types.DeriveSha(customData.Withdrawals, trie.NewStackTrie(nil)) + customPayloadHeader.WithdrawalsHash = &h + } else if basePayload.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(basePayload.Withdrawals), trie.NewStackTrie(nil)) customPayloadHeader.WithdrawalsHash = &h } // Return the new payload - return &api.ExecutableData{ + result := &api.ExecutableData{ ParentHash: customPayloadHeader.ParentHash, FeeRecipient: customPayloadHeader.Coinbase, StateRoot: customPayloadHeader.Root, @@ -126,7 +124,13 @@ func CustomizePayload(basePayload *api.ExecutableData, customData *CustomPayload BaseFeePerGas: customPayloadHeader.BaseFee, BlockHash: customPayloadHeader.Hash(), Transactions: txs, - }, nil + } + if customData.Withdrawals != nil { + result.Withdrawals = customData.Withdrawals + } else if basePayload.Withdrawals != nil { + result.Withdrawals = basePayload.Withdrawals + } + return result, nil } func (customData *CustomPayloadData) String() string { diff --git a/simulators/ethereum/engine/test/expect.go b/simulators/ethereum/engine/test/expect.go index 39b0e13f11..4cd90448f0 100644 --- a/simulators/ethereum/engine/test/expect.go +++ b/simulators/ethereum/engine/test/expect.go @@ -71,11 +71,12 @@ func NewTestEngineClient(t *Env, ec client.EngineClient) *TestEngineClient { } } -// ForkchoiceUpdatedV1 +// ForkchoiceUpdated type ForkchoiceResponseExpectObject struct { *ExpectEnv Response api.ForkChoiceResponse + Version int Error error } @@ -86,6 +87,7 @@ func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV1(fcState *api.Forkchoi return &ForkchoiceResponseExpectObject{ ExpectEnv: &ExpectEnv{tec.Env}, Response: resp, + Version: 1, Error: err, } } @@ -97,39 +99,40 @@ func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV2(fcState *api.Forkchoi return &ForkchoiceResponseExpectObject{ ExpectEnv: &ExpectEnv{tec.Env}, Response: resp, + Version: 2, Error: err, } } func (exp *ForkchoiceResponseExpectObject) ExpectNoError() { if exp.Error != nil { - exp.Fatalf("FAIL (%s): Unexpected error on EngineForkchoiceUpdatedV1: %v, expected=", exp.TestName, exp.Error) + exp.Fatalf("FAIL (%s): Unexpected error on EngineForkchoiceUpdatedV%d: %v, expected=", exp.TestName, exp.Version, exp.Error) } } func (exp *ForkchoiceResponseExpectObject) ExpectNoValidationError() { if exp.Response.PayloadStatus.ValidationError != nil { - exp.Fatalf("FAIL (%s): Unexpected validation error on EngineForkchoiceUpdatedV1: %v, expected=", exp.TestName, exp.Response.PayloadStatus.ValidationError) + exp.Fatalf("FAIL (%s): Unexpected validation error on EngineForkchoiceUpdatedV%d: %v, expected=", exp.TestName, exp.Version, exp.Response.PayloadStatus.ValidationError) } } func (exp *ForkchoiceResponseExpectObject) ExpectError() { if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineForkchoiceUpdatedV1: response=%v", exp.TestName, exp.Response) + exp.Fatalf("FAIL (%s): Expected error on EngineForkchoiceUpdatedV%d: response=%v", exp.TestName, exp.Version, exp.Response) } } func (exp *ForkchoiceResponseExpectObject) ExpectErrorCode(code int) { // TODO: Actually check error code if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineForkchoiceUpdatedV1: response=%v", exp.TestName, exp.Response) + exp.Fatalf("FAIL (%s): Expected error on EngineForkchoiceUpdatedV%d: response=%v", exp.TestName, exp.Version, exp.Response) } } func (exp *ForkchoiceResponseExpectObject) ExpectPayloadStatus(ps PayloadStatus) { exp.ExpectNoError() if PayloadStatus(exp.Response.PayloadStatus.Status) != ps { - exp.Fatalf("FAIL (%s): Unexpected status response on EngineForkchoiceUpdatedV1: %v, expected=%v", exp.TestName, exp.Response.PayloadStatus.Status, ps) + exp.Fatalf("FAIL (%s): Unexpected status response on EngineForkchoiceUpdatedV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Response.PayloadStatus.Status, ps) } } @@ -140,14 +143,14 @@ func (exp *ForkchoiceResponseExpectObject) ExpectAnyPayloadStatus(statuses ...Pa return } } - exp.Fatalf("FAIL (%s): Unexpected status response on EngineForkchoiceUpdatedV1: %v, expected=%v", exp.TestName, exp.Response.PayloadStatus.Status, StatusesToString(statuses)) + exp.Fatalf("FAIL (%s): Unexpected status response on EngineForkchoiceUpdatedV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Response.PayloadStatus.Status, StatusesToString(statuses)) } func (exp *ForkchoiceResponseExpectObject) ExpectLatestValidHash(lvh *common.Hash) { exp.ExpectNoError() if ((lvh == nil || exp.Response.PayloadStatus.LatestValidHash == nil) && exp.Response.PayloadStatus.LatestValidHash != lvh) || (lvh != nil && exp.Response.PayloadStatus.LatestValidHash != nil && *exp.Response.PayloadStatus.LatestValidHash != *lvh) { - exp.Fatalf("FAIL (%v): Unexpected LatestValidHash on EngineForkchoiceUpdatedV1: %v, expected=%v", exp.TestName, exp.Response.PayloadStatus.LatestValidHash, lvh) + exp.Fatalf("FAIL (%v): Unexpected LatestValidHash on EngineForkchoiceUpdatedV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Response.PayloadStatus.LatestValidHash, lvh) } } @@ -155,16 +158,17 @@ func (exp *ForkchoiceResponseExpectObject) ExpectPayloadID(pid *api.PayloadID) { exp.ExpectNoError() if ((exp.Response.PayloadID == nil || pid == nil) && exp.Response.PayloadID != pid) || (exp.Response.PayloadID != nil && pid != nil && *exp.Response.PayloadID != *pid) { - exp.Fatalf("FAIL (%v): Unexpected PayloadID on EngineForkchoiceUpdatedV1: %v, expected=%v", exp.TestName, exp.Response.PayloadID, pid) + exp.Fatalf("FAIL (%v): Unexpected PayloadID on EngineForkchoiceUpdatedV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Response.PayloadID, pid) } } -// NewPayloadV1 +// NewPayload type NewPayloadResponseExpectObject struct { *ExpectEnv - Status api.PayloadStatusV1 - Error error + Status api.PayloadStatusV1 + Version int + Error error } func (tec *TestEngineClient) TestEngineNewPayloadV1(payload *api.ExecutableData) *NewPayloadResponseExpectObject { @@ -174,6 +178,7 @@ func (tec *TestEngineClient) TestEngineNewPayloadV1(payload *api.ExecutableData) return &NewPayloadResponseExpectObject{ ExpectEnv: &ExpectEnv{tec.Env}, Status: status, + Version: 1, Error: err, } } @@ -185,39 +190,40 @@ func (tec *TestEngineClient) TestEngineNewPayloadV2(payload *api.ExecutableData) return &NewPayloadResponseExpectObject{ ExpectEnv: &ExpectEnv{tec.Env}, Status: status, + Version: 2, Error: err, } } func (exp *NewPayloadResponseExpectObject) ExpectNoError() { if exp.Error != nil { - exp.Fatalf("FAIL (%s): Expected no error on EngineNewPayloadV1: error=%v", exp.TestName, exp.Error) + exp.Fatalf("FAIL (%s): Expected no error on EngineNewPayloadV%d: error=%v", exp.TestName, exp.Version, exp.Error) } } func (exp *NewPayloadResponseExpectObject) ExpectError() { if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV1: status=%v", exp.TestName, exp.Status) + exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV%d: status=%v", exp.TestName, exp.Version, exp.Status) } } func (exp *NewPayloadResponseExpectObject) ExpectErrorCode(code int) { // TODO: Actually check error code if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV1: status=%v", exp.TestName, exp.Status) + exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV%d: status=%v", exp.TestName, exp.Version, exp.Status) } } func (exp *NewPayloadResponseExpectObject) ExpectNoValidationError() { if exp.Status.ValidationError != nil { - exp.Fatalf("FAIL (%s): Unexpected validation error on EngineNewPayloadV1: %v, expected=", exp.TestName, exp.Status.ValidationError) + exp.Fatalf("FAIL (%s): Unexpected validation error on EngineNewPayloadV%d: %v, expected=", exp.TestName, exp.Version, exp.Status.ValidationError) } } func (exp *NewPayloadResponseExpectObject) ExpectStatus(ps PayloadStatus) { exp.ExpectNoError() if PayloadStatus(exp.Status.Status) != ps { - exp.Fatalf("FAIL (%s): Unexpected status response on EngineNewPayloadV1: %v, expected=%v", exp.TestName, exp.Status.Status, ps) + exp.Fatalf("FAIL (%s): Unexpected status response on EngineNewPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Status.Status, ps) } } @@ -229,14 +235,14 @@ func (exp *NewPayloadResponseExpectObject) ExpectStatusEither(statuses ...Payloa } } - exp.Fatalf("FAIL (%s): Unexpected status response on EngineNewPayloadV1: %v, expected=%v", exp.TestName, exp.Status.Status, strings.Join(StatusesToString(statuses), ",")) + exp.Fatalf("FAIL (%s): Unexpected status response on EngineNewPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Status.Status, strings.Join(StatusesToString(statuses), ",")) } func (exp *NewPayloadResponseExpectObject) ExpectLatestValidHash(lvh *common.Hash) { exp.ExpectNoError() if ((lvh == nil || exp.Status.LatestValidHash == nil) && exp.Status.LatestValidHash != lvh) || (lvh != nil && exp.Status.LatestValidHash != nil && *exp.Status.LatestValidHash != *lvh) { - exp.Fatalf("FAIL (%v): Unexpected LatestValidHash on EngineNewPayloadV1: %v, expected=%v", exp.TestName, exp.Status.LatestValidHash, lvh) + exp.Fatalf("FAIL (%v): Unexpected LatestValidHash on EngineNewPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Status.LatestValidHash, lvh) } } @@ -455,6 +461,15 @@ func (exp *BlockResponseExpectObject) ExpectHash(expHash common.Hash) { } } +func (exp *BlockResponseExpectObject) ExpectWithdrawalsRoot(expectedRoot *common.Hash) { + exp.ExpectNoError() + actualWithdrawalsRoot := exp.Block.Header().WithdrawalsHash + if ((expectedRoot == nil || actualWithdrawalsRoot == nil) && actualWithdrawalsRoot != expectedRoot) || + (expectedRoot != nil && actualWithdrawalsRoot != nil && *actualWithdrawalsRoot != *expectedRoot) { + exp.Fatalf("FAIL (%s): Unexpected WithdrawalsRoot on %s: %v, expected=%v", exp.TestName, exp.Call, actualWithdrawalsRoot, expectedRoot) + } +} + func (exp *BlockResponseExpectObject) ExpectTransactionCountEqual(expTxCount int) { exp.ExpectNoError() if len(exp.Block.Transactions()) != expTxCount { diff --git a/simulators/ethereum/engine/test/spec.go b/simulators/ethereum/engine/test/spec.go index 046c53e1c1..a7f5d13741 100644 --- a/simulators/ethereum/engine/test/spec.go +++ b/simulators/ethereum/engine/test/spec.go @@ -21,9 +21,6 @@ type Spec struct { // Default: 0 TTD int64 - // Shanghai Timestamp - ShanghaiTimestamp *big.Int - // CL Mocker configuration for slots to `safe` and `finalized` respectively SlotsToSafe *big.Int SlotsToFinalized *big.Int @@ -49,4 +46,8 @@ type Spec struct { // Transaction type to use throughout the test TestTransactionType helper.TestTransactionType + + // Fork Config + // Shanghai Fork Timestamp + ShanghaiTimestamp *big.Int } From 4c525d3eef1b006a7f32aa137a6677ff95992898 Mon Sep 17 00:00:00 2001 From: marioevz Date: Sat, 19 Nov 2022 11:46:46 -0600 Subject: [PATCH 07/49] simulators/ethereum/engine: Fix request payload --- simulators/ethereum/engine/suites/engine/tests.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simulators/ethereum/engine/suites/engine/tests.go b/simulators/ethereum/engine/suites/engine/tests.go index d71d43c465..81ead251ae 100644 --- a/simulators/ethereum/engine/suites/engine/tests.go +++ b/simulators/ethereum/engine/suites/engine/tests.go @@ -2803,7 +2803,7 @@ func transactionReorg(t *test.Env) { OnPayloadProducerSelected: func() { // At this point we have not broadcast the transaction, // therefore any payload we get should not contain any - t.CLMock.GetNextPayloadID() + t.CLMock.RequestNextPayload() t.CLMock.GetNextPayload() noTxnPayload = t.CLMock.LatestPayloadBuilt if len(noTxnPayload.Transactions) != 0 { @@ -2907,7 +2907,7 @@ func transactionReorgBlockhash(newNPOnRevert bool) func(t *test.Env) { OnPayloadProducerSelected: func() { // At this point we have not broadcast the transaction, // therefore any payload we get should not contain any - t.CLMock.GetNextPayloadID() + t.CLMock.RequestNextPayload() t.CLMock.GetNextPayload() // Create the transaction From 1e92ad67c4fb16e552a212d00be3ad82c0077ec7 Mon Sep 17 00:00:00 2001 From: marioevz Date: Sat, 19 Nov 2022 11:47:08 -0600 Subject: [PATCH 08/49] simulators/ethereum/engine: Withdrawals tests --- .../engine/suites/withdrawals/tests.go | 132 ++++++++++++++++-- 1 file changed, 117 insertions(+), 15 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 682d0cc051..225eb39cba 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -4,8 +4,11 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/hive/simulators/ethereum/engine/clmock" + "github.com/ethereum/hive/simulators/ethereum/engine/globals" + "github.com/ethereum/hive/simulators/ethereum/engine/helper" "github.com/ethereum/hive/simulators/ethereum/engine/test" ) @@ -23,20 +26,101 @@ var ( var Tests = []test.Spec{ { - Name: "Sanity Withdrawal Test", - Run: sanityWithdrawalTest, + Name: "Shanghai Genesis", + Run: sanityWithdrawalTestGen(globals.GenesisTimestamp, 1), TTD: 0, - ShanghaiTimestamp: big.NewInt(0), + ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp), // Genesis reaches shanghai }, + { + Name: "Shanghai on Block 1", + Run: sanityWithdrawalTestGen(globals.GenesisTimestamp+1, 1), + TTD: 0, + ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + 1), + }, + { + Name: "Shanghai on Block 2", + Run: sanityWithdrawalTestGen(globals.GenesisTimestamp+2, 1), + TTD: 0, + ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + 2), + }, + + /* + { + Name: "Send Withdrawal Pre-Shanghai", + Run: sanityWithdrawalTest, + TTD: 0, + ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + 5), + }, + */ } -// Make a simple withdrawal and verify balance change on FCU. -func sanityWithdrawalTest(t *test.Env) { - t.CLMock.WaitForTTD() - withdrawalAddress := common.HexToAddress("0xAA") - withdrawalAmount := big.NewInt(100) - t.CLMock.ProduceSingleBlock(clmock.BlockProcessCallbacks{ - OnPayloadProducerSelected: func() { +// Wait for Shanghai and perform a simple withdrawal. +func sanityWithdrawalTestGen(shanghaiTimestamp int64, postShanghaiBlocks int) func(t *test.Env) { + return func(t *test.Env) { + t.CLMock.WaitForTTD() + + // Check if we have pre-Shanghai blocks + if shanghaiTimestamp > globals.GenesisTimestamp { + // Check `latest` during all pre-shanghai blocks, none should + // contain `withdrawalsRoot`, including genesis. + + // Genesis should not contain `withdrawalsRoot` either + r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectWithdrawalsRoot(nil) + + preShanghaiBlocks := shanghaiTimestamp - globals.GenesisTimestamp - 1 + if preShanghaiBlocks > 0 { + t.CLMock.ProduceBlocks(int(preShanghaiBlocks), clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + // Try to send a ForkchoiceUpdatedV2 before Shanghai + r := t.TestEngine.TestEngineForkchoiceUpdatedV2( + &beacon.ForkchoiceStateV1{ + HeadBlockHash: t.CLMock.LatestHeader.Hash(), + }, + &beacon.PayloadAttributes{ + Timestamp: t.CLMock.LatestHeader.Time + 1, + Random: common.Hash{}, + SuggestedFeeRecipient: common.Address{}, + Withdrawals: make(types.Withdrawals, 0), + }, + ) + r.ExpectError() + }, + OnGetPayload: func() { + // Send produced payload but try to include non-nil + // `withdrawals`, it should fail. + emptyWithdrawalsList := make(types.Withdrawals, 0) + payloadPlusWithdrawals, err := helper.CustomizePayload(&t.CLMock.LatestPayloadBuilt, &helper.CustomPayloadData{ + Withdrawals: emptyWithdrawalsList, + }) + if err != nil { + t.Fatalf("Unable to append withdrawals: %v", err) + } + r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) + r.ExpectStatus(test.Invalid) + }, + }) + } + + } else { + // Genesis is post shanghai, it should contain EmptyWithdrawalsRoot + r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectWithdrawalsRoot(&types.EmptyRootHash) + } + + /* + // Verify `latest` block does not have withdrawalsRoot + p := t.TestEngine.TestBlockByNumber(nil) + if p.Block.Header().WithdrawalsHash != nil { + t.Fatalf("FAIL (%s): Pre-Shanghai Block contains withdrawalsRoot: %v", + t.TestName, + p.Block.Header().WithdrawalsHash, + ) + } + + // Perform a withdrawal on the first Shanghai block + withdrawalAddress := common.HexToAddress("0xAA") + withdrawalAmount := big.NewInt(100) withdrawals := make(types.Withdrawals, 0) withdrawals = append(withdrawals, &types.Withdrawal{ Index: 0, @@ -44,10 +128,28 @@ func sanityWithdrawalTest(t *test.Env) { Address: withdrawalAddress, Amount: withdrawalAmount, }) - t.CLMock.SetNextWithdrawals(withdrawals) - }, - }) - r := t.TestEngine.TestBalanceAt(withdrawalAddress, nil) - r.ExpectBalanceEqual(withdrawalAmount) + t.CLMock.ProduceSingleBlock(clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + t.CLMock.SetNextWithdrawals(withdrawals) + }, + }) + + // Check new balance of the withdrawal account + r := t.TestEngine.TestBalanceAt(withdrawalAddress, nil) + r.ExpectBalanceEqual(withdrawalAmount) + + // Check withdrawalsRoot of `eth_getBlockByNumber` + p = t.TestEngine.TestBlockByNumber(nil) + expectedWithdrawalsRoot := types.DeriveSha(types.Withdrawals(withdrawals), trie.NewStackTrie(nil)) + gotWithdrawalsRoot := p.Block.Header().WithdrawalsHash + if gotWithdrawalsRoot == nil || *gotWithdrawalsRoot != expectedWithdrawalsRoot { + t.Fatalf("FAIL (%s): Incorrect withdrawalsRoot: %v!=%v", + t.TestName, + gotWithdrawalsRoot, + expectedWithdrawalsRoot, + ) + } + */ + } } From 6afbbf4f810c0ecf6cf58e37e12517902099832d Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 21 Nov 2022 11:49:47 -0600 Subject: [PATCH 09/49] simulators/ethereum/engine: Withdrawals tests update --- .../engine/suites/withdrawals/tests.go | 191 +++++++++++++----- 1 file changed, 135 insertions(+), 56 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 225eb39cba..27860faf44 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/hive/simulators/ethereum/engine/clmock" "github.com/ethereum/hive/simulators/ethereum/engine/globals" "github.com/ethereum/hive/simulators/ethereum/engine/helper" @@ -13,8 +14,6 @@ import ( ) var ( - big0 = new(big.Int) - big1 = big.NewInt(1) Head *big.Int // Nil Pending = big.NewInt(-2) Finalized = big.NewInt(-3) @@ -27,35 +26,62 @@ var ( var Tests = []test.Spec{ { Name: "Shanghai Genesis", - Run: sanityWithdrawalTestGen(globals.GenesisTimestamp, 1), + Run: basicWithdrawalsTestGen(globals.GenesisTimestamp, 1, 16), TTD: 0, ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp), // Genesis reaches shanghai }, { Name: "Shanghai on Block 1", - Run: sanityWithdrawalTestGen(globals.GenesisTimestamp+1, 1), + Run: basicWithdrawalsTestGen(globals.GenesisTimestamp+1, 1, 16), TTD: 0, ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + 1), }, { Name: "Shanghai on Block 2", - Run: sanityWithdrawalTestGen(globals.GenesisTimestamp+2, 1), + Run: basicWithdrawalsTestGen(globals.GenesisTimestamp+2, 1, 16), TTD: 0, ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + 2), }, +} - /* - { - Name: "Send Withdrawal Pre-Shanghai", - Run: sanityWithdrawalTest, - TTD: 0, - ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + 5), - }, - */ +// Test types +type WithdrawalsHistory map[uint64]types.Withdrawals + +func (wh WithdrawalsHistory) GetExpectedAccountBalance(account common.Address, block uint64) *big.Int { + balance := big.NewInt(0) + for b := uint64(0); b <= block; b++ { + if withdrawals, ok := wh[b]; ok { + for _, withdrawal := range withdrawals { + if withdrawal.Address == account { + balance.Add(balance, withdrawal.Amount) + } + } + } + } + return balance +} + +func (wh WithdrawalsHistory) GetAddressesWithdrawnOnBlock(block uint64) []common.Address { + addressMap := make(map[common.Address]bool) + if withdrawals, ok := wh[block]; ok { + for _, withdrawal := range withdrawals { + addressMap[withdrawal.Address] = true + } + } + addressList := make([]common.Address, 0) + for addr := range addressMap { + addressList = append(addressList, addr) + } + return addressList +} + +type WithdrawalsTestSpec struct { + ShanghaiTimestamp int64 + WithdrawalsBlockCount int } // Wait for Shanghai and perform a simple withdrawal. -func sanityWithdrawalTestGen(shanghaiTimestamp int64, postShanghaiBlocks int) func(t *test.Env) { +func basicWithdrawalsTestGen(shanghaiTimestamp int64, postShanghaiBlocks int, withdrawalsPerBlock int) func(t *test.Env) { return func(t *test.Env) { t.CLMock.WaitForTTD() @@ -72,7 +98,8 @@ func sanityWithdrawalTestGen(shanghaiTimestamp int64, postShanghaiBlocks int) fu if preShanghaiBlocks > 0 { t.CLMock.ProduceBlocks(int(preShanghaiBlocks), clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { - // Try to send a ForkchoiceUpdatedV2 before Shanghai + // Try to send a ForkchoiceUpdatedV2 with non-null + // withdrawals before Shanghai r := t.TestEngine.TestEngineForkchoiceUpdatedV2( &beacon.ForkchoiceStateV1{ HeadBlockHash: t.CLMock.LatestHeader.Hash(), @@ -99,6 +126,12 @@ func sanityWithdrawalTestGen(shanghaiTimestamp int64, postShanghaiBlocks int) fu r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) r.ExpectStatus(test.Invalid) }, + OnNewPayloadBroadcast: func() { + // We sent a pre-shanghai FCU. + // Keep expecting `nil` until Shanghai. + r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectWithdrawalsRoot(nil) + }, }) } @@ -108,48 +141,94 @@ func sanityWithdrawalTestGen(shanghaiTimestamp int64, postShanghaiBlocks int) fu r.ExpectWithdrawalsRoot(&types.EmptyRootHash) } - /* - // Verify `latest` block does not have withdrawalsRoot - p := t.TestEngine.TestBlockByNumber(nil) - if p.Block.Header().WithdrawalsHash != nil { - t.Fatalf("FAIL (%s): Pre-Shanghai Block contains withdrawalsRoot: %v", - t.TestName, - p.Block.Header().WithdrawalsHash, - ) + // Produce requested post-shanghai blocks + // (At least 1 block will be produced after this procedure ends). + var ( + withdrawnAccounts = make(map[common.Address]bool) + withdrawalsHistory = make(WithdrawalsHistory) + startAccount = int64(0x100) + nextAccount = startAccount + nextIndex = uint64(0) + differentAccounts = int64(16) + firstWithdrawalBlock = t.CLMock.LatestExecutedPayload.Number + 1 + lastWithdrawals types.Withdrawals + ) + + t.CLMock.ProduceBlocks(postShanghaiBlocks, clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + // Send some withdrawals + nextWithdrawals := make(types.Withdrawals, 0) + for i := 0; i < withdrawalsPerBlock; i++ { + nextWithdrawal := &types.Withdrawal{ + Index: nextIndex, + Validator: uint64(nextIndex), + Address: common.BigToAddress(big.NewInt(nextAccount)), + Amount: big.NewInt(1), + } + nextWithdrawals = append(nextWithdrawals, nextWithdrawal) + withdrawnAccounts[common.BigToAddress(big.NewInt(nextAccount))] = true + nextIndex++ + nextAccount = (((nextAccount - startAccount) + 1) % differentAccounts) + startAccount + } + t.CLMock.NextWithdrawals = nextWithdrawals + withdrawalsHistory[t.CLMock.LatestHeader.Number.Uint64()+1] = nextWithdrawals + lastWithdrawals = nextWithdrawals + }, + OnGetPayload: func() { + // Send new payload with `withdrawals=null` and expect error + }, + OnNewPayloadBroadcast: func() { + // Check withdrawal addresses and verify withdrawal balances + // have not yet been applied + for _, addr := range withdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { + // Test balance at `latest`, which should not yet have the + // withdrawal applied. + r := t.TestEngine.TestBalanceAt(addr, nil) + r.ExpectBalanceEqual( + withdrawalsHistory.GetExpectedAccountBalance( + addr, + t.CLMock.LatestExecutedPayload.Number-1)) + } + }, + OnForkchoiceBroadcast: func() { + // Check withdrawal addresses and verify withdrawal balances + // have been applied + for _, addr := range withdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { + // Test balance at `latest`, which should not yet have the + // withdrawal applied. + r := t.TestEngine.TestBalanceAt(addr, nil) + r.ExpectBalanceEqual( + withdrawalsHistory.GetExpectedAccountBalance( + addr, + t.CLMock.LatestExecutedPayload.Number)) + } + // Check the correct withdrawal root on `latest` block + r := t.TestEngine.TestBlockByNumber(nil) + expectedWithdrawalsRoot := types.DeriveSha(lastWithdrawals, trie.NewStackTrie(nil)) + r.ExpectWithdrawalsRoot(&expectedWithdrawalsRoot) + }, + }) + // Iterate over balance history of withdrawn accounts using RPC and + // check that the balances match expected values. + // Also check one block before the withdrawal took place, verify that + // withdrawal has not been updated. + for block := firstWithdrawalBlock - 1; block <= firstWithdrawalBlock+uint64(postShanghaiBlocks)-1; block++ { + for withdrawnAcc := range withdrawnAccounts { + r := t.TestEngine.TestBalanceAt(withdrawnAcc, big.NewInt(int64(block))) + r.ExpectBalanceEqual( + withdrawalsHistory.GetExpectedAccountBalance( + withdrawnAcc, + block)) } - - // Perform a withdrawal on the first Shanghai block - withdrawalAddress := common.HexToAddress("0xAA") - withdrawalAmount := big.NewInt(100) - withdrawals := make(types.Withdrawals, 0) - withdrawals = append(withdrawals, &types.Withdrawal{ - Index: 0, - Validator: 0, - Address: withdrawalAddress, - Amount: withdrawalAmount, - }) - - t.CLMock.ProduceSingleBlock(clmock.BlockProcessCallbacks{ - OnPayloadProducerSelected: func() { - t.CLMock.SetNextWithdrawals(withdrawals) - }, - }) - - // Check new balance of the withdrawal account - r := t.TestEngine.TestBalanceAt(withdrawalAddress, nil) - r.ExpectBalanceEqual(withdrawalAmount) - - // Check withdrawalsRoot of `eth_getBlockByNumber` - p = t.TestEngine.TestBlockByNumber(nil) - expectedWithdrawalsRoot := types.DeriveSha(types.Withdrawals(withdrawals), trie.NewStackTrie(nil)) - gotWithdrawalsRoot := p.Block.Header().WithdrawalsHash - if gotWithdrawalsRoot == nil || *gotWithdrawalsRoot != expectedWithdrawalsRoot { - t.Fatalf("FAIL (%s): Incorrect withdrawalsRoot: %v!=%v", - t.TestName, - gotWithdrawalsRoot, - expectedWithdrawalsRoot, - ) + // Check the correct withdrawal root on past blocks + r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) + var expectedWithdrawalsRoot *common.Hash = nil + if block >= firstWithdrawalBlock { + calcWithdrawalsRoot := types.DeriveSha(lastWithdrawals, trie.NewStackTrie(nil)) + expectedWithdrawalsRoot = &calcWithdrawalsRoot } - */ + r.ExpectWithdrawalsRoot(expectedWithdrawalsRoot) + + } } } From 06501a29ffbe506c729804a5a4b40a456b27f608 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 21 Nov 2022 14:46:13 -0600 Subject: [PATCH 10/49] simulators/ethereum/engine: Test spec interface --- simulators/ethereum/engine/main.go | 42 +++++++------- simulators/ethereum/engine/test/env.go | 16 ++++-- simulators/ethereum/engine/test/spec.go | 75 ++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 30 deletions(-) diff --git a/simulators/ethereum/engine/main.go b/simulators/ethereum/engine/main.go index ebf72c0849..67f3672a6e 100644 --- a/simulators/ethereum/engine/main.go +++ b/simulators/ethereum/engine/main.go @@ -10,10 +10,6 @@ import ( "github.com/ethereum/hive/simulators/ethereum/engine/helper" "github.com/ethereum/hive/simulators/ethereum/engine/test" - suite_auth "github.com/ethereum/hive/simulators/ethereum/engine/suites/auth" - suite_engine "github.com/ethereum/hive/simulators/ethereum/engine/suites/engine" - suite_sync "github.com/ethereum/hive/simulators/ethereum/engine/suites/sync" - suite_transition "github.com/ethereum/hive/simulators/ethereum/engine/suites/transition" suite_withdrawals "github.com/ethereum/hive/simulators/ethereum/engine/suites/withdrawals" ) @@ -50,10 +46,10 @@ func main() { simulator := hivesim.New() - addTestsToSuite(&engine, suite_engine.Tests, "full") - addTestsToSuite(&transition, suite_transition.Tests, "full") - addTestsToSuite(&auth, suite_auth.Tests, "full") - suite_sync.AddSyncTestsToSuite(simulator, &sync, suite_sync.Tests) + //addTestsToSuite(&engine, suite_engine.Tests, "full") + //addTestsToSuite(&transition, suite_transition.Tests, "full") + //addTestsToSuite(&auth, suite_auth.Tests, "full") + //suite_sync.AddSyncTestsToSuite(simulator, &sync, suite_sync.Tests) addTestsToSuite(&withdrawals, suite_withdrawals.Tests, "full") // Mark suites for execution @@ -65,54 +61,54 @@ func main() { } // Add test cases to a given test suite -func addTestsToSuite(suite *hivesim.Suite, tests []test.Spec, nodeType string) { +func addTestsToSuite(suite *hivesim.Suite, tests []test.SpecInterface, nodeType string) { for _, currentTest := range tests { currentTest := currentTest genesisPath := "./init/genesis.json" // If the test.Spec specified a custom genesis file, use that instead. - if currentTest.GenesisFile != "" { - genesisPath = "./init/" + currentTest.GenesisFile + if currentTest.GetGenesisFile() != "" { + genesisPath = "./init/" + currentTest.GetGenesisFile() } // Load genesis for it to be modified before starting the client testFiles := hivesim.Params{"/genesis.json": genesisPath} // Calculate and set the TTD for this test - ttd := helper.CalculateRealTTD(genesisPath, currentTest.TTD) + ttd := helper.CalculateRealTTD(genesisPath, currentTest.GetTTD()) // Configure Forks newParams := globals.DefaultClientEnv.Set("HIVE_TERMINAL_TOTAL_DIFFICULTY", fmt.Sprintf("%d", ttd)) - if currentTest.ShanghaiTimestamp != nil { - newParams = newParams.Set("HIVE_SHANGHAI_TIMESTAMP", fmt.Sprintf("%d", currentTest.ShanghaiTimestamp)) + if currentTest.GetForkConfig().ShanghaiTimestamp != nil { + newParams = newParams.Set("HIVE_SHANGHAI_TIMESTAMP", fmt.Sprintf("%d", currentTest.GetForkConfig().ShanghaiTimestamp)) } if nodeType != "" { newParams = newParams.Set("HIVE_NODETYPE", nodeType) } - if currentTest.ChainFile != "" { + if currentTest.GetChainFile() != "" { // We are using a Proof of Work chain file, remove all clique-related settings // TODO: Nethermind still requires HIVE_MINER for the Engine API // delete(newParams, "HIVE_MINER") delete(newParams, "HIVE_CLIQUE_PRIVATEKEY") delete(newParams, "HIVE_CLIQUE_PERIOD") // Add the new file to be loaded as chain.rlp - testFiles = testFiles.Set("/chain.rlp", "./chains/"+currentTest.ChainFile) + testFiles = testFiles.Set("/chain.rlp", "./chains/"+currentTest.GetChainFile()) } - if currentTest.DisableMining { + if currentTest.IsMiningDisabled() { delete(newParams, "HIVE_MINER") } suite.Add(hivesim.ClientTestSpec{ - Name: currentTest.Name, - Description: currentTest.About, + Name: currentTest.GetName(), + Description: currentTest.GetAbout(), Parameters: newParams, Files: testFiles, Run: func(t *hivesim.T, c *hivesim.Client) { - t.Logf("Start test (%s): %s", c.Type, currentTest.Name) + t.Logf("Start test (%s): %s", c.Type, currentTest.GetName()) defer func() { - t.Logf("End test (%s): %s", c.Type, currentTest.Name) + t.Logf("End test (%s): %s", c.Type, currentTest.GetName()) }() timeout := globals.DefaultTestCaseTimeout // If a test.Spec specifies a timeout, use that instead - if currentTest.TimeoutSeconds != 0 { - timeout = time.Second * time.Duration(currentTest.TimeoutSeconds) + if currentTest.GetTimeout() != 0 { + timeout = time.Second * time.Duration(currentTest.GetTimeout()) } // Run the test case test.Run(currentTest, big.NewInt(ttd), timeout, t, c, newParams, testFiles) diff --git a/simulators/ethereum/engine/test/env.go b/simulators/ethereum/engine/test/env.go index 311dfb13dd..5657d69178 100644 --- a/simulators/ethereum/engine/test/env.go +++ b/simulators/ethereum/engine/test/env.go @@ -45,9 +45,15 @@ type Env struct { TestTransactionType helper.TestTransactionType } -func Run(testSpec Spec, ttd *big.Int, timeout time.Duration, t *hivesim.T, c *hivesim.Client, cParams hivesim.Params, cFiles hivesim.Params) { +func Run(testSpec SpecInterface, ttd *big.Int, timeout time.Duration, t *hivesim.T, c *hivesim.Client, cParams hivesim.Params, cFiles hivesim.Params) { // Setup the CL Mocker for this test - clMocker := clmock.NewCLMocker(t, testSpec.SlotsToSafe, testSpec.SlotsToFinalized, big.NewInt(testSpec.SafeSlotsToImportOptimistically), testSpec.ShanghaiTimestamp) + consensusConfig := testSpec.GetConsensusConfig() + clMocker := clmock.NewCLMocker( + t, + consensusConfig.SlotsToSafe, + consensusConfig.SlotsToFinalized, + big.NewInt(consensusConfig.SafeSlotsToImportOptimistically), + testSpec.GetForkConfig().ShanghaiTimestamp) // Defer closing all clients defer func() { clMocker.CloseClients() @@ -66,7 +72,7 @@ func Run(testSpec Spec, ttd *big.Int, timeout time.Duration, t *hivesim.T, c *hi env := &Env{ T: t, - TestName: testSpec.Name, + TestName: testSpec.GetName(), Client: c, Engine: ec, Eth: ec, @@ -74,7 +80,7 @@ func Run(testSpec Spec, ttd *big.Int, timeout time.Duration, t *hivesim.T, c *hi CLMock: clMocker, ClientParams: cParams, ClientFiles: cFiles, - TestTransactionType: testSpec.TestTransactionType, + TestTransactionType: testSpec.GetTestTransactionType(), } // Before running the test, make sure Eth and Engine ports are open for the client @@ -106,7 +112,7 @@ func Run(testSpec Spec, ttd *big.Int, timeout time.Duration, t *hivesim.T, c *hi }() // Run the test - testSpec.Run(env) + testSpec.Execute(env) } func (t *Env) MainTTD() *big.Int { diff --git a/simulators/ethereum/engine/test/spec.go b/simulators/ethereum/engine/test/spec.go index a7f5d13741..c325c4e97d 100644 --- a/simulators/ethereum/engine/test/spec.go +++ b/simulators/ethereum/engine/test/spec.go @@ -6,6 +6,31 @@ import ( "github.com/ethereum/hive/simulators/ethereum/engine/helper" ) +type ForkConfig struct { + // Shanghai Fork Timestamp + ShanghaiTimestamp *big.Int +} + +type ConsensusConfig struct { + SlotsToSafe *big.Int + SlotsToFinalized *big.Int + SafeSlotsToImportOptimistically int64 +} + +type SpecInterface interface { + Execute(*Env) + GetAbout() string + GetConsensusConfig() ConsensusConfig + GetChainFile() string + GetForkConfig() ForkConfig + GetGenesisFile() string + GetName() string + GetTestTransactionType() helper.TestTransactionType + GetTimeout() int + GetTTD() int64 + IsMiningDisabled() bool +} + type Spec struct { // Name of the test Name string @@ -48,6 +73,52 @@ type Spec struct { TestTransactionType helper.TestTransactionType // Fork Config - // Shanghai Fork Timestamp - ShanghaiTimestamp *big.Int + ForkConfig +} + +func (s Spec) Execute(env *Env) { + s.Run(env) +} + +func (s Spec) GetAbout() string { + return s.About +} + +func (s Spec) GetConsensusConfig() ConsensusConfig { + return ConsensusConfig{ + SlotsToSafe: s.SlotsToSafe, + SlotsToFinalized: s.SlotsToFinalized, + SafeSlotsToImportOptimistically: s.SafeSlotsToImportOptimistically, + } +} +func (s Spec) GetChainFile() string { + return s.ChainFile +} + +func (s Spec) GetForkConfig() ForkConfig { + return s.ForkConfig +} + +func (s Spec) GetGenesisFile() string { + return s.GenesisFile +} + +func (s Spec) GetName() string { + return s.Name +} + +func (s Spec) GetTestTransactionType() helper.TestTransactionType { + return s.TestTransactionType +} + +func (s Spec) GetTimeout() int { + return s.TimeoutSeconds +} + +func (s Spec) GetTTD() int64 { + return s.TTD +} + +func (s Spec) IsMiningDisabled() bool { + return s.DisableMining } From 19e0ac341f85c4ef39001b2dde016ff4e370f530 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 21 Nov 2022 14:46:33 -0600 Subject: [PATCH 11/49] simulators/ethereum/engine: Add withdrawals test cases --- .../engine/suites/withdrawals/tests.go | 418 +++++++++++------- 1 file changed, 259 insertions(+), 159 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 27860faf44..c9f0e1fe69 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -1,3 +1,4 @@ +// # Test suite for withdrawals tests package suite_withdrawals import ( @@ -23,30 +24,93 @@ var ( // Execution specification reference: // https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md -var Tests = []test.Spec{ - { - Name: "Shanghai Genesis", - Run: basicWithdrawalsTestGen(globals.GenesisTimestamp, 1, 16), - TTD: 0, - ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp), // Genesis reaches shanghai +// List of all withdrawals tests +var Tests = []test.SpecInterface{ + WithdrawalsBasicSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork On Genesis", + About: ` + Tests the withdrawals fork happening since genesis (e.g. on a + testnet). + `, + }, + PreWithdrawalsBlocks: 0, + WithdrawalsBlockCount: 2, // Genesis is a withdrawals block + WithdrawalsPerBlock: 16, }, - { - Name: "Shanghai on Block 1", - Run: basicWithdrawalsTestGen(globals.GenesisTimestamp+1, 1, 16), - TTD: 0, - ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + 1), + + WithdrawalsBasicSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Block 1", + About: ` + Tests the withdrawals fork happening directly after genesis. + `, + }, + PreWithdrawalsBlocks: 1, // Only Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 1, + WithdrawalsPerBlock: 16, + }, + + WithdrawalsBasicSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Block 2", + About: ` + Tests the transition to the withdrawals fork after a single block + has happened. + `, + }, + PreWithdrawalsBlocks: 2, // Genesis and Block 1 are Pre-Withdrawals + WithdrawalsBlockCount: 1, + WithdrawalsPerBlock: 16, + }, + WithdrawalsBasicSpec{ + Spec: test.Spec{ + Name: "Withdraw to a single account", + About: ` + Make multiple withdrawals to a single account. + `, + }, + PreWithdrawalsBlocks: 1, + WithdrawalsBlockCount: 1, + WithdrawalsPerBlock: 64, + WithdrawableAccountCount: 1, }, - { - Name: "Shanghai on Block 2", - Run: basicWithdrawalsTestGen(globals.GenesisTimestamp+2, 1, 16), - TTD: 0, - ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + 2), + WithdrawalsBasicSpec{ + Spec: test.Spec{ + Name: "Withdraw to two accounts", + About: ` + Make multiple withdrawals to two different accounts, repeated in + round-robin. + Reasoning: There might be a difference in implementation when an + account appears multiple times in the withdrawals list but the list + is not in ordered sequence. + `, + }, + PreWithdrawalsBlocks: 1, + WithdrawalsBlockCount: 1, + WithdrawalsPerBlock: 64, + WithdrawableAccountCount: 2, + }, + WithdrawalsBasicSpec{ + Spec: test.Spec{ + Name: "Withdraw to many accounts", + About: ` + Make multiple withdrawals to 1024 different accounts. + Execute many blocks this way. + `, + }, + PreWithdrawalsBlocks: 1, + WithdrawalsBlockCount: 16, + WithdrawalsPerBlock: 1024, + WithdrawableAccountCount: 1024, }, } -// Test types +// Helper structure used to keep history of the amounts withdrawn to each test account. type WithdrawalsHistory map[uint64]types.Withdrawals +// Gets an account expected value for a given block, taking into account all +// withdrawals that credited the account. func (wh WithdrawalsHistory) GetExpectedAccountBalance(account common.Address, block uint64) *big.Int { balance := big.NewInt(0) for b := uint64(0); b <= block; b++ { @@ -61,6 +125,7 @@ func (wh WithdrawalsHistory) GetExpectedAccountBalance(account common.Address, b return balance } +// Get a list of all addresses that were credited by withdrawals on a given block. func (wh WithdrawalsHistory) GetAddressesWithdrawnOnBlock(block uint64) []common.Address { addressMap := make(map[common.Address]bool) if withdrawals, ok := wh[block]; ok { @@ -75,160 +140,195 @@ func (wh WithdrawalsHistory) GetAddressesWithdrawnOnBlock(block uint64) []common return addressList } -type WithdrawalsTestSpec struct { - ShanghaiTimestamp int64 - WithdrawalsBlockCount int +// Get the withdrawals list for a given block. +func (wh WithdrawalsHistory) GetWithdrawals(block uint64) types.Withdrawals { + if w, ok := wh[block]; ok { + return w + } + return make(types.Withdrawals, 0) +} + +// Withdrawals basic spec: +// Specifies a simple withdrawals test where the withdrawals fork can +type WithdrawalsBasicSpec struct { + test.Spec + PreWithdrawalsBlocks int64 // Number of blocks before withdrawals fork activation + WithdrawalsBlockCount int // Number of blocks on and after withdrawals fork activation + WithdrawalsPerBlock int // Number of withdrawals per block + WithdrawableAccountCount int64 // Number of accounts to withdraw to (round-robin) } -// Wait for Shanghai and perform a simple withdrawal. -func basicWithdrawalsTestGen(shanghaiTimestamp int64, postShanghaiBlocks int, withdrawalsPerBlock int) func(t *test.Env) { - return func(t *test.Env) { - t.CLMock.WaitForTTD() +// Calculates Shanghai fork timestamp given the amount of blocks that need to be +// produced beforehand (CLMock produces 1 block each second). +func (ws WithdrawalsBasicSpec) GetForkConfig() test.ForkConfig { + return test.ForkConfig{ + ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + ws.PreWithdrawalsBlocks), + } +} - // Check if we have pre-Shanghai blocks - if shanghaiTimestamp > globals.GenesisTimestamp { - // Check `latest` during all pre-shanghai blocks, none should - // contain `withdrawalsRoot`, including genesis. +// Wait for Shanghai and perform some withdrawals. +func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { + t.CLMock.WaitForTTD() - // Genesis should not contain `withdrawalsRoot` either - r := t.TestEngine.TestBlockByNumber(nil) - r.ExpectWithdrawalsRoot(nil) - - preShanghaiBlocks := shanghaiTimestamp - globals.GenesisTimestamp - 1 - if preShanghaiBlocks > 0 { - t.CLMock.ProduceBlocks(int(preShanghaiBlocks), clmock.BlockProcessCallbacks{ - OnPayloadProducerSelected: func() { - // Try to send a ForkchoiceUpdatedV2 with non-null - // withdrawals before Shanghai - r := t.TestEngine.TestEngineForkchoiceUpdatedV2( - &beacon.ForkchoiceStateV1{ - HeadBlockHash: t.CLMock.LatestHeader.Hash(), - }, - &beacon.PayloadAttributes{ - Timestamp: t.CLMock.LatestHeader.Time + 1, - Random: common.Hash{}, - SuggestedFeeRecipient: common.Address{}, - Withdrawals: make(types.Withdrawals, 0), - }, - ) - r.ExpectError() - }, - OnGetPayload: func() { - // Send produced payload but try to include non-nil - // `withdrawals`, it should fail. - emptyWithdrawalsList := make(types.Withdrawals, 0) - payloadPlusWithdrawals, err := helper.CustomizePayload(&t.CLMock.LatestPayloadBuilt, &helper.CustomPayloadData{ - Withdrawals: emptyWithdrawalsList, - }) - if err != nil { - t.Fatalf("Unable to append withdrawals: %v", err) - } - r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) - r.ExpectStatus(test.Invalid) - }, - OnNewPayloadBroadcast: func() { - // We sent a pre-shanghai FCU. - // Keep expecting `nil` until Shanghai. - r := t.TestEngine.TestBlockByNumber(nil) - r.ExpectWithdrawalsRoot(nil) - }, - }) - } + shanghaiTimestamp := globals.GenesisTimestamp + ws.PreWithdrawalsBlocks - } else { - // Genesis is post shanghai, it should contain EmptyWithdrawalsRoot - r := t.TestEngine.TestBlockByNumber(nil) - r.ExpectWithdrawalsRoot(&types.EmptyRootHash) - } + // Check if we have pre-Shanghai blocks + if shanghaiTimestamp > globals.GenesisTimestamp { + // Check `latest` during all pre-shanghai blocks, none should + // contain `withdrawalsRoot`, including genesis. - // Produce requested post-shanghai blocks - // (At least 1 block will be produced after this procedure ends). - var ( - withdrawnAccounts = make(map[common.Address]bool) - withdrawalsHistory = make(WithdrawalsHistory) - startAccount = int64(0x100) - nextAccount = startAccount - nextIndex = uint64(0) - differentAccounts = int64(16) - firstWithdrawalBlock = t.CLMock.LatestExecutedPayload.Number + 1 - lastWithdrawals types.Withdrawals - ) - - t.CLMock.ProduceBlocks(postShanghaiBlocks, clmock.BlockProcessCallbacks{ - OnPayloadProducerSelected: func() { - // Send some withdrawals - nextWithdrawals := make(types.Withdrawals, 0) - for i := 0; i < withdrawalsPerBlock; i++ { - nextWithdrawal := &types.Withdrawal{ - Index: nextIndex, - Validator: uint64(nextIndex), - Address: common.BigToAddress(big.NewInt(nextAccount)), - Amount: big.NewInt(1), + // Genesis should not contain `withdrawalsRoot` either + r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectWithdrawalsRoot(nil) + + preShanghaiBlocks := shanghaiTimestamp - globals.GenesisTimestamp - 1 + if preShanghaiBlocks > 0 { + t.CLMock.ProduceBlocks(int(preShanghaiBlocks), clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + // Try to send a ForkchoiceUpdatedV2 with non-null + // withdrawals before Shanghai + r := t.TestEngine.TestEngineForkchoiceUpdatedV2( + &beacon.ForkchoiceStateV1{ + HeadBlockHash: t.CLMock.LatestHeader.Hash(), + }, + &beacon.PayloadAttributes{ + Timestamp: t.CLMock.LatestHeader.Time + 1, + Random: common.Hash{}, + SuggestedFeeRecipient: common.Address{}, + Withdrawals: make(types.Withdrawals, 0), + }, + ) + r.ExpectError() + }, + OnGetPayload: func() { + // Send produced payload but try to include non-nil + // `withdrawals`, it should fail. + emptyWithdrawalsList := make(types.Withdrawals, 0) + payloadPlusWithdrawals, err := helper.CustomizePayload(&t.CLMock.LatestPayloadBuilt, &helper.CustomPayloadData{ + Withdrawals: emptyWithdrawalsList, + }) + if err != nil { + t.Fatalf("Unable to append withdrawals: %v", err) } - nextWithdrawals = append(nextWithdrawals, nextWithdrawal) - withdrawnAccounts[common.BigToAddress(big.NewInt(nextAccount))] = true - nextIndex++ - nextAccount = (((nextAccount - startAccount) + 1) % differentAccounts) + startAccount - } - t.CLMock.NextWithdrawals = nextWithdrawals - withdrawalsHistory[t.CLMock.LatestHeader.Number.Uint64()+1] = nextWithdrawals - lastWithdrawals = nextWithdrawals - }, - OnGetPayload: func() { - // Send new payload with `withdrawals=null` and expect error - }, - OnNewPayloadBroadcast: func() { - // Check withdrawal addresses and verify withdrawal balances - // have not yet been applied - for _, addr := range withdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { - // Test balance at `latest`, which should not yet have the - // withdrawal applied. - r := t.TestEngine.TestBalanceAt(addr, nil) - r.ExpectBalanceEqual( - withdrawalsHistory.GetExpectedAccountBalance( - addr, - t.CLMock.LatestExecutedPayload.Number-1)) - } - }, - OnForkchoiceBroadcast: func() { - // Check withdrawal addresses and verify withdrawal balances - // have been applied - for _, addr := range withdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { - // Test balance at `latest`, which should not yet have the - // withdrawal applied. - r := t.TestEngine.TestBalanceAt(addr, nil) - r.ExpectBalanceEqual( - withdrawalsHistory.GetExpectedAccountBalance( - addr, - t.CLMock.LatestExecutedPayload.Number)) + r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) + r.ExpectStatus(test.Invalid) + }, + OnNewPayloadBroadcast: func() { + // We sent a pre-shanghai FCU. + // Keep expecting `nil` until Shanghai. + r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectWithdrawalsRoot(nil) + }, + }) + } + + } else { + // Genesis is post shanghai, it should contain EmptyWithdrawalsRoot + r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectWithdrawalsRoot(&types.EmptyRootHash) + } + + // Produce requested post-shanghai blocks + // (At least 1 block will be produced after this procedure ends). + var ( + withdrawnAccounts = make(map[common.Address]bool) + withdrawalsHistory = make(WithdrawalsHistory) + startAccount = int64(0x100) + nextAccount = startAccount + nextIndex = uint64(0) + differentAccounts = ws.WithdrawableAccountCount + firstWithdrawalBlock = uint64(shanghaiTimestamp - globals.GenesisTimestamp) + ) + + if differentAccounts == 0 { + // We can't have 0 different accounts to withdraw to + differentAccounts = int64(16) + } + + t.CLMock.ProduceBlocks(ws.WithdrawalsBlockCount, clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + // Send some withdrawals + nextWithdrawals := make(types.Withdrawals, 0) + for i := 0; i < ws.WithdrawalsPerBlock; i++ { + nextWithdrawal := &types.Withdrawal{ + Index: nextIndex, + Validator: uint64(nextIndex), + Address: common.BigToAddress(big.NewInt(nextAccount)), + Amount: big.NewInt(1), } - // Check the correct withdrawal root on `latest` block - r := t.TestEngine.TestBlockByNumber(nil) - expectedWithdrawalsRoot := types.DeriveSha(lastWithdrawals, trie.NewStackTrie(nil)) - r.ExpectWithdrawalsRoot(&expectedWithdrawalsRoot) - }, - }) - // Iterate over balance history of withdrawn accounts using RPC and - // check that the balances match expected values. - // Also check one block before the withdrawal took place, verify that - // withdrawal has not been updated. - for block := firstWithdrawalBlock - 1; block <= firstWithdrawalBlock+uint64(postShanghaiBlocks)-1; block++ { - for withdrawnAcc := range withdrawnAccounts { - r := t.TestEngine.TestBalanceAt(withdrawnAcc, big.NewInt(int64(block))) + nextWithdrawals = append(nextWithdrawals, nextWithdrawal) + withdrawnAccounts[common.BigToAddress(big.NewInt(nextAccount))] = true + nextIndex++ + nextAccount = (((nextAccount - startAccount) + 1) % differentAccounts) + startAccount + } + t.CLMock.NextWithdrawals = nextWithdrawals + withdrawalsHistory[t.CLMock.LatestHeader.Number.Uint64()+1] = nextWithdrawals + }, + OnGetPayload: func() { + // Send new payload with `withdrawals=null` and expect error + }, + OnNewPayloadBroadcast: func() { + // Check withdrawal addresses and verify withdrawal balances + // have not yet been applied + for _, addr := range withdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { + // Test balance at `latest`, which should not yet have the + // withdrawal applied. + r := t.TestEngine.TestBalanceAt(addr, nil) r.ExpectBalanceEqual( withdrawalsHistory.GetExpectedAccountBalance( - withdrawnAcc, - block)) + addr, + t.CLMock.LatestExecutedPayload.Number-1), + ) } - // Check the correct withdrawal root on past blocks - r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) - var expectedWithdrawalsRoot *common.Hash = nil - if block >= firstWithdrawalBlock { - calcWithdrawalsRoot := types.DeriveSha(lastWithdrawals, trie.NewStackTrie(nil)) - expectedWithdrawalsRoot = &calcWithdrawalsRoot + }, + OnForkchoiceBroadcast: func() { + // Check withdrawal addresses and verify withdrawal balances + // have been applied + for _, addr := range withdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { + // Test balance at `latest`, which should not yet have the + // withdrawal applied. + r := t.TestEngine.TestBalanceAt(addr, nil) + r.ExpectBalanceEqual( + withdrawalsHistory.GetExpectedAccountBalance( + addr, + t.CLMock.LatestExecutedPayload.Number), + ) } - r.ExpectWithdrawalsRoot(expectedWithdrawalsRoot) - + // Check the correct withdrawal root on `latest` block + r := t.TestEngine.TestBlockByNumber(nil) + expectedWithdrawalsRoot := types.DeriveSha( + withdrawalsHistory.GetWithdrawals(t.CLMock.LatestExecutedPayload.Number), + trie.NewStackTrie(nil), + ) + r.ExpectWithdrawalsRoot(&expectedWithdrawalsRoot) + }, + }) + // Iterate over balance history of withdrawn accounts using RPC and + // check that the balances match expected values. + // Also check one block before the withdrawal took place, verify that + // withdrawal has not been updated. + for block := uint64(0); block <= firstWithdrawalBlock+uint64(ws.WithdrawalsBlockCount)-1; block++ { + for withdrawnAcc := range withdrawnAccounts { + r := t.TestEngine.TestBalanceAt(withdrawnAcc, big.NewInt(int64(block))) + r.ExpectBalanceEqual( + withdrawalsHistory.GetExpectedAccountBalance( + withdrawnAcc, + block, + ), + ) + } + // Check the correct withdrawal root on past blocks + r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) + var expectedWithdrawalsRoot *common.Hash = nil + t.Logf("INFO (%s): firstWithdrawalBlock=%d, block=%d", t.TestName, firstWithdrawalBlock, block) + if block >= firstWithdrawalBlock { + calcWithdrawalsRoot := types.DeriveSha( + withdrawalsHistory.GetWithdrawals(block), + trie.NewStackTrie(nil), + ) + expectedWithdrawalsRoot = &calcWithdrawalsRoot } + r.ExpectWithdrawalsRoot(expectedWithdrawalsRoot) + } } From 178e3a0d6b28dbe2d1f0b6300b82c89aba503e46 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 21 Nov 2022 15:03:48 -0600 Subject: [PATCH 12/49] simulators/ethereum/engine: Withdraw many accounts fix --- simulators/ethereum/engine/suites/withdrawals/tests.go | 5 +++-- simulators/ethereum/engine/test/expect.go | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index c9f0e1fe69..4af06b1189 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -93,11 +93,12 @@ var Tests = []test.SpecInterface{ }, WithdrawalsBasicSpec{ Spec: test.Spec{ - Name: "Withdraw to many accounts", + Name: "Withdraw many accounts", About: ` Make multiple withdrawals to 1024 different accounts. Execute many blocks this way. `, + TimeoutSeconds: 120, }, PreWithdrawalsBlocks: 1, WithdrawalsBlockCount: 16, @@ -233,7 +234,7 @@ func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { var ( withdrawnAccounts = make(map[common.Address]bool) withdrawalsHistory = make(WithdrawalsHistory) - startAccount = int64(0x100) + startAccount = int64(0x1000) nextAccount = startAccount nextIndex = uint64(0) differentAccounts = ws.WithdrawableAccountCount diff --git a/simulators/ethereum/engine/test/expect.go b/simulators/ethereum/engine/test/expect.go index 4cd90448f0..ba14435d39 100644 --- a/simulators/ethereum/engine/test/expect.go +++ b/simulators/ethereum/engine/test/expect.go @@ -496,6 +496,8 @@ func (exp *BlockResponseExpectObject) ExpectCoinbase(expCoinbase common.Address) type BalanceResponseExpectObject struct { *ExpectEnv Call string + Account common.Address + Block *big.Int Balance *big.Int Error error } @@ -507,7 +509,9 @@ func (tec *TestEngineClient) TestBalanceAt(account common.Address, number *big.I return &BalanceResponseExpectObject{ ExpectEnv: &ExpectEnv{tec.Env}, Call: "BalanceAt", + Account: account, Balance: balance, + Block: number, Error: err, } } @@ -522,7 +526,7 @@ func (exp *BalanceResponseExpectObject) ExpectBalanceEqual(expBalance *big.Int) exp.ExpectNoError() if ((expBalance == nil || exp.Balance == nil) && expBalance != exp.Balance) || (expBalance != nil && exp.Balance != nil && expBalance.Cmp(exp.Balance) != 0) { - exp.Fatalf("FAIL (%s): Unexpected balance on %s: %v, expected=%v", exp.TestName, exp.Call, exp.Balance, expBalance) + exp.Fatalf("FAIL (%s): Unexpected balance on %s, for account %s at block %v: %v, expected=%v", exp.TestName, exp.Call, exp.Account, exp.Block, exp.Balance, expBalance) } } From 12afc8770b88befee58f9457810c0283656f17e6 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 21 Nov 2022 16:25:19 -0600 Subject: [PATCH 13/49] clients/nethermind: Temporarily remove MergeForkIdTransition --- clients/nethermind/mapper.jq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/nethermind/mapper.jq b/clients/nethermind/mapper.jq index 4340fd6f1d..4af69952c1 100644 --- a/clients/nethermind/mapper.jq +++ b/clients/nethermind/mapper.jq @@ -134,7 +134,7 @@ def clique_engine: "eip3198Transition": env.HIVE_FORK_LONDON|to_hex, # Merge - "MergeForkIdTransition": env.HIVE_MERGE_BLOCK_ID|to_hex, + # "MergeForkIdTransition": env.HIVE_MERGE_BLOCK_ID|to_hex, # Shanghai "eip4895TransitionTimestamp": env.HIVE_SHANGHAI_TIMESTAMP|to_hex, From fa13e38144307a4115e2aa554df5d1d8bcf3ff19 Mon Sep 17 00:00:00 2001 From: marioevz Date: Tue, 22 Nov 2022 15:14:17 -0600 Subject: [PATCH 14/49] simulators/ethereum/engine: Withdrawals sync tests --- .../engine/suites/withdrawals/tests.go | 232 +++++++++++++++--- 1 file changed, 198 insertions(+), 34 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 4af06b1189..296f72abde 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -3,11 +3,13 @@ package suite_withdrawals import ( "math/big" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/hive/simulators/ethereum/engine/client/hive_rpc" "github.com/ethereum/hive/simulators/ethereum/engine/clmock" "github.com/ethereum/hive/simulators/ethereum/engine/globals" "github.com/ethereum/hive/simulators/ethereum/engine/helper" @@ -26,7 +28,7 @@ var ( // List of all withdrawals tests var Tests = []test.SpecInterface{ - WithdrawalsBasicSpec{ + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork On Genesis", About: ` @@ -39,7 +41,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 16, }, - WithdrawalsBasicSpec{ + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 1", About: ` @@ -51,7 +53,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 16, }, - WithdrawalsBasicSpec{ + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 2", About: ` @@ -63,7 +65,7 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 1, WithdrawalsPerBlock: 16, }, - WithdrawalsBasicSpec{ + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw to a single account", About: ` @@ -75,7 +77,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 64, WithdrawableAccountCount: 1, }, - WithdrawalsBasicSpec{ + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw to two accounts", About: ` @@ -91,7 +93,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 64, WithdrawableAccountCount: 2, }, - WithdrawalsBasicSpec{ + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw many accounts", About: ` @@ -105,6 +107,83 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 1024, WithdrawableAccountCount: 1024, }, + // Sync Tests + WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account", + About: ` + - Spawn a first client + - Go through withdrawals fork on Block 1 + - Withdraw to a single account 16 times each block for 8 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance + `, + }, + PreWithdrawalsBlocks: 1, + WithdrawalsBlockCount: 2, + WithdrawalsPerBlock: 16, + WithdrawableAccountCount: 1, + }, + SyncSteps: 1, + }, + WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Sync after 2 blocks - Withdrawals on Genesis - Single Withdrawal Account", + About: ` + - Spawn a first client, with Withdrawals since genesis + - Withdraw to a single account 16 times each block for 8 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance + `, + }, + PreWithdrawalsBlocks: 0, + WithdrawalsBlockCount: 2, + WithdrawalsPerBlock: 16, + WithdrawableAccountCount: 1, + }, + SyncSteps: 1, + }, + WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Sync after 2 blocks - Withdrawals on Block 2 - Single Withdrawal Account", + About: ` + - Spawn a first client + - Go through withdrawals fork on Block 2 + - Withdraw to a single account 16 times each block for 8 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync, which include syncing a pre-Withdrawals block, and verify withdrawn account's balance + `, + }, + PreWithdrawalsBlocks: 2, + WithdrawalsBlockCount: 2, + WithdrawalsPerBlock: 16, + WithdrawableAccountCount: 1, + }, + SyncSteps: 1, + }, + WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Sync after 128 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts", + About: ` + - Spawn a first client + - Go through withdrawals fork on Block 2 + - Withdraw to a single account 16 times each block for 8 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync, which include syncing a pre-Withdrawals block, and verify withdrawn account's balance + `, + TimeoutSeconds: 300, + }, + PreWithdrawalsBlocks: 2, + WithdrawalsBlockCount: 128, + WithdrawalsPerBlock: 16, + WithdrawableAccountCount: 1024, + }, + SyncSteps: 1, + }, } // Helper structure used to keep history of the amounts withdrawn to each test account. @@ -149,26 +228,56 @@ func (wh WithdrawalsHistory) GetWithdrawals(block uint64) types.Withdrawals { return make(types.Withdrawals, 0) } -// Withdrawals basic spec: -// Specifies a simple withdrawals test where the withdrawals fork can -type WithdrawalsBasicSpec struct { +// Get the withdrawn accounts list until a given block height. +func (wh WithdrawalsHistory) GetWithdrawnAccounts(blockHeight uint64) map[common.Address]*big.Int { + accounts := make(map[common.Address]*big.Int) + for block := uint64(0); block <= blockHeight; block++ { + if withdrawals, ok := wh[block]; ok { + for _, withdrawal := range withdrawals { + if currentBalance, ok2 := accounts[withdrawal.Address]; ok2 { + currentBalance.Add(currentBalance, withdrawal.Amount) + } else { + accounts[withdrawal.Address] = new(big.Int).Set(withdrawal.Amount) + } + } + } + } + return accounts +} + +// Verify all withdrawals on a client at a given height +func (wh WithdrawalsHistory) VerifyWithdrawals(block uint64, rpcBlock *big.Int, testEngine *test.TestEngineClient) { + accounts := wh.GetWithdrawnAccounts(block) + for account, expectedBalance := range accounts { + r := testEngine.TestBalanceAt(account, rpcBlock) + r.ExpectBalanceEqual(expectedBalance) + } +} + +// Withdrawals base spec: +// Specifies a simple withdrawals test where the withdrawals fork can happen +// on genesis or afterwards. +type WithdrawalsBaseSpec struct { test.Spec - PreWithdrawalsBlocks int64 // Number of blocks before withdrawals fork activation - WithdrawalsBlockCount int // Number of blocks on and after withdrawals fork activation - WithdrawalsPerBlock int // Number of withdrawals per block - WithdrawableAccountCount int64 // Number of accounts to withdraw to (round-robin) + PreWithdrawalsBlocks int64 // Number of blocks before withdrawals fork activation + WithdrawalsBlockCount int // Number of blocks on and after withdrawals fork activation + WithdrawalsPerBlock int // Number of withdrawals per block + WithdrawableAccountCount int64 // Number of accounts to withdraw to (round-robin) + WithdrawalsHistory WithdrawalsHistory // Internal withdrawals history that keeps track of all withdrawals } // Calculates Shanghai fork timestamp given the amount of blocks that need to be // produced beforehand (CLMock produces 1 block each second). -func (ws WithdrawalsBasicSpec) GetForkConfig() test.ForkConfig { +func (ws WithdrawalsBaseSpec) GetForkConfig() test.ForkConfig { return test.ForkConfig{ ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + ws.PreWithdrawalsBlocks), } } -// Wait for Shanghai and perform some withdrawals. -func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { +func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { + // Create the withdrawals history object + ws.WithdrawalsHistory = make(WithdrawalsHistory) + t.CLMock.WaitForTTD() shanghaiTimestamp := globals.GenesisTimestamp + ws.PreWithdrawalsBlocks @@ -233,7 +342,6 @@ func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { // (At least 1 block will be produced after this procedure ends). var ( withdrawnAccounts = make(map[common.Address]bool) - withdrawalsHistory = make(WithdrawalsHistory) startAccount = int64(0x1000) nextAccount = startAccount nextIndex = uint64(0) @@ -263,7 +371,7 @@ func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { nextAccount = (((nextAccount - startAccount) + 1) % differentAccounts) + startAccount } t.CLMock.NextWithdrawals = nextWithdrawals - withdrawalsHistory[t.CLMock.LatestHeader.Number.Uint64()+1] = nextWithdrawals + ws.WithdrawalsHistory[t.CLMock.LatestHeader.Number.Uint64()+1] = nextWithdrawals }, OnGetPayload: func() { // Send new payload with `withdrawals=null` and expect error @@ -271,12 +379,12 @@ func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { OnNewPayloadBroadcast: func() { // Check withdrawal addresses and verify withdrawal balances // have not yet been applied - for _, addr := range withdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { + for _, addr := range ws.WithdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { // Test balance at `latest`, which should not yet have the // withdrawal applied. r := t.TestEngine.TestBalanceAt(addr, nil) r.ExpectBalanceEqual( - withdrawalsHistory.GetExpectedAccountBalance( + ws.WithdrawalsHistory.GetExpectedAccountBalance( addr, t.CLMock.LatestExecutedPayload.Number-1), ) @@ -285,12 +393,12 @@ func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { OnForkchoiceBroadcast: func() { // Check withdrawal addresses and verify withdrawal balances // have been applied - for _, addr := range withdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { + for _, addr := range ws.WithdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { // Test balance at `latest`, which should not yet have the // withdrawal applied. r := t.TestEngine.TestBalanceAt(addr, nil) r.ExpectBalanceEqual( - withdrawalsHistory.GetExpectedAccountBalance( + ws.WithdrawalsHistory.GetExpectedAccountBalance( addr, t.CLMock.LatestExecutedPayload.Number), ) @@ -298,7 +406,7 @@ func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { // Check the correct withdrawal root on `latest` block r := t.TestEngine.TestBlockByNumber(nil) expectedWithdrawalsRoot := types.DeriveSha( - withdrawalsHistory.GetWithdrawals(t.CLMock.LatestExecutedPayload.Number), + ws.WithdrawalsHistory.GetWithdrawals(t.CLMock.LatestExecutedPayload.Number), trie.NewStackTrie(nil), ) r.ExpectWithdrawalsRoot(&expectedWithdrawalsRoot) @@ -308,23 +416,16 @@ func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { // check that the balances match expected values. // Also check one block before the withdrawal took place, verify that // withdrawal has not been updated. - for block := uint64(0); block <= firstWithdrawalBlock+uint64(ws.WithdrawalsBlockCount)-1; block++ { - for withdrawnAcc := range withdrawnAccounts { - r := t.TestEngine.TestBalanceAt(withdrawnAcc, big.NewInt(int64(block))) - r.ExpectBalanceEqual( - withdrawalsHistory.GetExpectedAccountBalance( - withdrawnAcc, - block, - ), - ) - } + for block := uint64(0); block <= t.CLMock.LatestExecutedPayload.Number; block++ { + ws.WithdrawalsHistory.VerifyWithdrawals(block, big.NewInt(int64(block)), t.TestEngine) + // Check the correct withdrawal root on past blocks r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) var expectedWithdrawalsRoot *common.Hash = nil t.Logf("INFO (%s): firstWithdrawalBlock=%d, block=%d", t.TestName, firstWithdrawalBlock, block) if block >= firstWithdrawalBlock { calcWithdrawalsRoot := types.DeriveSha( - withdrawalsHistory.GetWithdrawals(block), + ws.WithdrawalsHistory.GetWithdrawals(block), trie.NewStackTrie(nil), ) expectedWithdrawalsRoot = &calcWithdrawalsRoot @@ -332,4 +433,67 @@ func (ws WithdrawalsBasicSpec) Execute(t *test.Env) { r.ExpectWithdrawalsRoot(expectedWithdrawalsRoot) } + + // Verify on `latest` + ws.WithdrawalsHistory.VerifyWithdrawals(t.CLMock.LatestExecutedPayload.Number, nil, t.TestEngine) +} + +// Withdrawals sync spec: +// Specifies a withdrawals test where the withdrawals happen and then a +// client needs to sync and apply the withdrawals. +type WithdrawalsSyncSpec struct { + WithdrawalsBaseSpec + SyncSteps int // Sync block chunks that will be passed as head through FCUs to the syncing client + SyncShouldFail bool // +} + +func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { + // Do the base withdrawal test first + ws.WithdrawalsBaseSpec.Execute(t) + + // Spawn a secondary client which will need to sync to the primary client + secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles, t.Engine) + + if err != nil { + t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) + } + secondaryEngineTest := test.NewTestEngineClient(t, secondaryEngine) + t.CLMock.AddEngineClient(secondaryEngine) + + if ws.SyncSteps > 1 { + // TODO + } else { + // Send the FCU to trigger sync on the secondary client + loop: + for { + select { + case <-t.TimeoutContext.Done(): + t.Fatalf("FAIL (%s): Timeout while waiting for secondary client to sync", t.TestName) + case <-time.After(time.Second): + secondaryEngineTest.TestEngineNewPayloadV2( + &t.CLMock.LatestExecutedPayload, + ) + r := secondaryEngineTest.TestEngineForkchoiceUpdatedV2( + &t.CLMock.LatestForkchoice, + &beacon.PayloadAttributes{}, + ) + if r.Response.PayloadStatus.Status == test.Valid { + break loop + } + } + } + + ws.WithdrawalsHistory.VerifyWithdrawals(t.CLMock.LatestHeader.Nonce.Uint64(), nil, secondaryEngineTest) + + } +} + +// 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 WithdrawalsReorgSpec struct { + WithdrawalsBaseSpec + ReOrgOriginBlockHeight uint64 // Height of the block where the re-org will happen + ReOrgDestBlockHeight uint64 // Height of the block to which the re-org will rollback } From 12dd0ae79b7fb2d3f9e6205bea9e874bb7e6260c Mon Sep 17 00:00:00 2001 From: marioevz Date: Wed, 23 Nov 2022 17:32:18 -0600 Subject: [PATCH 15/49] simulators/ethereum/engine: Withdrawals re-org --- simulators/ethereum/engine/clmock/clmock.go | 15 +- .../engine/suites/withdrawals/tests.go | 472 ++++++++++++++---- simulators/ethereum/engine/test/expect.go | 40 +- 3 files changed, 426 insertions(+), 101 deletions(-) diff --git a/simulators/ethereum/engine/clmock/clmock.go b/simulators/ethereum/engine/clmock/clmock.go index ccefd577bb..e17b382877 100644 --- a/simulators/ethereum/engine/clmock/clmock.go +++ b/simulators/ethereum/engine/clmock/clmock.go @@ -43,9 +43,10 @@ type CLMocker struct { PayloadProductionClientDelay time.Duration // Block Production State - NextBlockProducer client.EngineClient - NextFeeRecipient common.Address - NextPayloadID *api.PayloadID + NextBlockProducer client.EngineClient + NextFeeRecipient common.Address + NextPayloadID *api.PayloadID + CurrentPayloadNumber uint64 // PoS Chain History Information PrevRandaoHistory map[uint64]common.Hash @@ -406,7 +407,7 @@ func (cl *CLMocker) broadcastLatestForkchoice() { type BlockProcessCallbacks struct { OnPayloadProducerSelected func() - OnGetPayloadID func() + OnRequestNextPayload func() OnGetPayload func() OnNewPayloadBroadcast func() OnForkchoiceBroadcast func() @@ -424,6 +425,8 @@ func (cl *CLMocker) ProduceSingleBlock(callbacks BlockProcessCallbacks) { cl.EngineClientsLock.Lock() defer cl.EngineClientsLock.Unlock() + cl.CurrentPayloadNumber = cl.LatestHeader.Number.Uint64() + 1 + cl.pickNextPayloadProducer() // Check if next withdrawals necessary, test can override this value on @@ -440,8 +443,8 @@ func (cl *CLMocker) ProduceSingleBlock(callbacks BlockProcessCallbacks) { cl.SetNextWithdrawals(nil) - if callbacks.OnGetPayloadID != nil { - callbacks.OnGetPayloadID() + if callbacks.OnRequestNextPayload != nil { + callbacks.OnRequestNextPayload() } // Give the client a delay between getting the payload ID and actually retrieving the payload diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 296f72abde..1796a3ff3f 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -36,7 +36,7 @@ var Tests = []test.SpecInterface{ testnet). `, }, - PreWithdrawalsBlocks: 0, + WithdrawalsForkHeight: 0, WithdrawalsBlockCount: 2, // Genesis is a withdrawals block WithdrawalsPerBlock: 16, }, @@ -48,7 +48,7 @@ var Tests = []test.SpecInterface{ Tests the withdrawals fork happening directly after genesis. `, }, - PreWithdrawalsBlocks: 1, // Only Genesis is Pre-Withdrawals + WithdrawalsForkHeight: 1, // Only Genesis is Pre-Withdrawals WithdrawalsBlockCount: 1, WithdrawalsPerBlock: 16, }, @@ -61,7 +61,7 @@ var Tests = []test.SpecInterface{ has happened. `, }, - PreWithdrawalsBlocks: 2, // Genesis and Block 1 are Pre-Withdrawals + WithdrawalsForkHeight: 2, // Genesis and Block 1 are Pre-Withdrawals WithdrawalsBlockCount: 1, WithdrawalsPerBlock: 16, }, @@ -72,7 +72,7 @@ var Tests = []test.SpecInterface{ Make multiple withdrawals to a single account. `, }, - PreWithdrawalsBlocks: 1, + WithdrawalsForkHeight: 1, WithdrawalsBlockCount: 1, WithdrawalsPerBlock: 64, WithdrawableAccountCount: 1, @@ -88,7 +88,7 @@ var Tests = []test.SpecInterface{ is not in ordered sequence. `, }, - PreWithdrawalsBlocks: 1, + WithdrawalsForkHeight: 1, WithdrawalsBlockCount: 1, WithdrawalsPerBlock: 64, WithdrawableAccountCount: 2, @@ -102,7 +102,7 @@ var Tests = []test.SpecInterface{ `, TimeoutSeconds: 120, }, - PreWithdrawalsBlocks: 1, + WithdrawalsForkHeight: 1, WithdrawalsBlockCount: 16, WithdrawalsPerBlock: 1024, WithdrawableAccountCount: 1024, @@ -120,7 +120,7 @@ var Tests = []test.SpecInterface{ - Wait for sync and verify withdrawn account's balance `, }, - PreWithdrawalsBlocks: 1, + WithdrawalsForkHeight: 1, WithdrawalsBlockCount: 2, WithdrawalsPerBlock: 16, WithdrawableAccountCount: 1, @@ -138,7 +138,7 @@ var Tests = []test.SpecInterface{ - Wait for sync and verify withdrawn account's balance `, }, - PreWithdrawalsBlocks: 0, + WithdrawalsForkHeight: 0, WithdrawalsBlockCount: 2, WithdrawalsPerBlock: 16, WithdrawableAccountCount: 1, @@ -157,7 +157,7 @@ var Tests = []test.SpecInterface{ - Wait for sync, which include syncing a pre-Withdrawals block, and verify withdrawn account's balance `, }, - PreWithdrawalsBlocks: 2, + WithdrawalsForkHeight: 2, WithdrawalsBlockCount: 2, WithdrawalsPerBlock: 16, WithdrawableAccountCount: 1, @@ -177,13 +177,63 @@ var Tests = []test.SpecInterface{ `, TimeoutSeconds: 300, }, - PreWithdrawalsBlocks: 2, + WithdrawalsForkHeight: 2, WithdrawalsBlockCount: 128, WithdrawalsPerBlock: 16, WithdrawableAccountCount: 1024, }, SyncSteps: 1, }, + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Block 1 - 1 Block Deep Re-Org", + About: ` + Tests a simple 1 block deep re-org + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 1, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 16, + WithdrawalsPerBlock: 16, + }, + ReOrgDepth: 1, + }, + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Block 1 - 8 Block Deep Re-Org NewPayload", + About: ` + Tests a 8 block deep re-org using NewPayload + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 1, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 16, + WithdrawalsPerBlock: 16, + }, + ReOrgDepth: 8, + }, + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Block 1 - 8 Block Deep Re-Org, Sync", + About: ` + Tests a 8 block deep re-org using sync + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 1, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 16, + WithdrawalsPerBlock: 16, + }, + ReOrgDepth: 8, + ReOrgViaSync: true, + }, + // REORG WHERE THE FORK HAPPENS ON A DIFFERENT BLOCK HEIGHT!!!! } // Helper structure used to keep history of the amounts withdrawn to each test account. @@ -254,24 +304,82 @@ func (wh WithdrawalsHistory) VerifyWithdrawals(block uint64, rpcBlock *big.Int, } } +// Create a new copy of the withdrawals history +func (wh WithdrawalsHistory) Copy() WithdrawalsHistory { + result := make(WithdrawalsHistory) + for k, v := range wh { + result[k] = v + } + return result +} + // Withdrawals base spec: // Specifies a simple withdrawals test where the withdrawals fork can happen // on genesis or afterwards. type WithdrawalsBaseSpec struct { test.Spec - PreWithdrawalsBlocks int64 // Number of blocks before withdrawals fork activation + WithdrawalsForkHeight int64 // Withdrawals activation fork height WithdrawalsBlockCount int // Number of blocks on and after withdrawals fork activation WithdrawalsPerBlock int // Number of withdrawals per block - WithdrawableAccountCount int64 // Number of accounts to withdraw to (round-robin) + WithdrawableAccountCount uint64 // Number of accounts to withdraw to (round-robin) WithdrawalsHistory WithdrawalsHistory // Internal withdrawals history that keeps track of all withdrawals + SkipBaseVerifications bool // For code reuse of the base spec procedure } // Calculates Shanghai fork timestamp given the amount of blocks that need to be // produced beforehand (CLMock produces 1 block each second). +func (ws WithdrawalsBaseSpec) WithdrawalsTimestamp() int64 { + return globals.GenesisTimestamp + ws.WithdrawalsForkHeight +} + +// Generates the fork config, including withdrawals fork timestamp. func (ws WithdrawalsBaseSpec) GetForkConfig() test.ForkConfig { return test.ForkConfig{ - ShanghaiTimestamp: big.NewInt(globals.GenesisTimestamp + ws.PreWithdrawalsBlocks), + ShanghaiTimestamp: big.NewInt(ws.WithdrawalsTimestamp()), + } +} + +// Number of blocks to be produced (not counting genesis) before withdrawals +// fork. +func (ws WithdrawalsBaseSpec) GetPreWithdrawalsBlockCount() int { + if ws.WithdrawalsForkHeight == 0 { + return 0 + } else { + return int(ws.WithdrawalsForkHeight - 1) + } +} + +// Number of payloads to be produced (pre and post withdrawals) during the entire test +func (ws WithdrawalsBaseSpec) GetTotalPayloadCount() int { + return ws.GetPreWithdrawalsBlockCount() + ws.WithdrawalsBlockCount +} + +func (ws WithdrawalsBaseSpec) GetWithdrawableAccountCount() uint64 { + if ws.WithdrawableAccountCount == 0 { + // Withdraw to 16 accounts by default + return 16 + } + return ws.WithdrawableAccountCount +} + +// Generates a list of withdrawals based on current configuration +func (ws WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, startAccount *big.Int) (types.Withdrawals, uint64) { + differentAccounts := ws.GetWithdrawableAccountCount() + + nextWithdrawals := make(types.Withdrawals, 0) + for i := 0; i < ws.WithdrawalsPerBlock; i++ { + nextAccount := new(big.Int).Set(startAccount) + nextAccount.Add(nextAccount, big.NewInt(int64(nextIndex%differentAccounts))) + nextWithdrawal := &types.Withdrawal{ + Index: nextIndex, + Validator: uint64(nextIndex), + Address: common.BigToAddress(nextAccount), + Amount: big.NewInt(1), + } + nextWithdrawals = append(nextWithdrawals, nextWithdrawal) + nextIndex++ } + return nextWithdrawals, nextIndex } func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { @@ -280,101 +388,74 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { t.CLMock.WaitForTTD() - shanghaiTimestamp := globals.GenesisTimestamp + ws.PreWithdrawalsBlocks - // Check if we have pre-Shanghai blocks - if shanghaiTimestamp > globals.GenesisTimestamp { + if ws.WithdrawalsTimestamp() > globals.GenesisTimestamp { // Check `latest` during all pre-shanghai blocks, none should // contain `withdrawalsRoot`, including genesis. // Genesis should not contain `withdrawalsRoot` either r := t.TestEngine.TestBlockByNumber(nil) r.ExpectWithdrawalsRoot(nil) - - preShanghaiBlocks := shanghaiTimestamp - globals.GenesisTimestamp - 1 - if preShanghaiBlocks > 0 { - t.CLMock.ProduceBlocks(int(preShanghaiBlocks), clmock.BlockProcessCallbacks{ - OnPayloadProducerSelected: func() { - // Try to send a ForkchoiceUpdatedV2 with non-null - // withdrawals before Shanghai - r := t.TestEngine.TestEngineForkchoiceUpdatedV2( - &beacon.ForkchoiceStateV1{ - HeadBlockHash: t.CLMock.LatestHeader.Hash(), - }, - &beacon.PayloadAttributes{ - Timestamp: t.CLMock.LatestHeader.Time + 1, - Random: common.Hash{}, - SuggestedFeeRecipient: common.Address{}, - Withdrawals: make(types.Withdrawals, 0), - }, - ) - r.ExpectError() - }, - OnGetPayload: func() { - // Send produced payload but try to include non-nil - // `withdrawals`, it should fail. - emptyWithdrawalsList := make(types.Withdrawals, 0) - payloadPlusWithdrawals, err := helper.CustomizePayload(&t.CLMock.LatestPayloadBuilt, &helper.CustomPayloadData{ - Withdrawals: emptyWithdrawalsList, - }) - if err != nil { - t.Fatalf("Unable to append withdrawals: %v", err) - } - r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) - r.ExpectStatus(test.Invalid) - }, - OnNewPayloadBroadcast: func() { - // We sent a pre-shanghai FCU. - // Keep expecting `nil` until Shanghai. - r := t.TestEngine.TestBlockByNumber(nil) - r.ExpectWithdrawalsRoot(nil) - }, - }) - } - } else { // Genesis is post shanghai, it should contain EmptyWithdrawalsRoot r := t.TestEngine.TestBlockByNumber(nil) r.ExpectWithdrawalsRoot(&types.EmptyRootHash) } + // Produce any blocks necessary to reach withdrawals fork + t.CLMock.ProduceBlocks(ws.GetPreWithdrawalsBlockCount(), clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + // Try to send a ForkchoiceUpdatedV2 with non-null + // withdrawals before Shanghai + r := t.TestEngine.TestEngineForkchoiceUpdatedV2( + &beacon.ForkchoiceStateV1{ + HeadBlockHash: t.CLMock.LatestHeader.Hash(), + }, + &beacon.PayloadAttributes{ + Timestamp: t.CLMock.LatestHeader.Time + 1, + Random: common.Hash{}, + SuggestedFeeRecipient: common.Address{}, + Withdrawals: make(types.Withdrawals, 0), + }, + ) + r.ExpectError() + }, + OnGetPayload: func() { + // Send produced payload but try to include non-nil + // `withdrawals`, it should fail. + emptyWithdrawalsList := make(types.Withdrawals, 0) + payloadPlusWithdrawals, err := helper.CustomizePayload(&t.CLMock.LatestPayloadBuilt, &helper.CustomPayloadData{ + Withdrawals: emptyWithdrawalsList, + }) + if err != nil { + t.Fatalf("Unable to append withdrawals: %v", err) + } + r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) + r.ExpectStatus(test.Invalid) + }, + OnNewPayloadBroadcast: func() { + // We sent a pre-shanghai FCU. + // Keep expecting `nil` until Shanghai. + r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectWithdrawalsRoot(nil) + }, + }) + // Produce requested post-shanghai blocks // (At least 1 block will be produced after this procedure ends). var ( - withdrawnAccounts = make(map[common.Address]bool) - startAccount = int64(0x1000) - nextAccount = startAccount - nextIndex = uint64(0) - differentAccounts = ws.WithdrawableAccountCount - firstWithdrawalBlock = uint64(shanghaiTimestamp - globals.GenesisTimestamp) + startAccount = big.NewInt(0x1000) + nextIndex = uint64(0) ) - if differentAccounts == 0 { - // We can't have 0 different accounts to withdraw to - differentAccounts = int64(16) - } - t.CLMock.ProduceBlocks(ws.WithdrawalsBlockCount, clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { // Send some withdrawals - nextWithdrawals := make(types.Withdrawals, 0) - for i := 0; i < ws.WithdrawalsPerBlock; i++ { - nextWithdrawal := &types.Withdrawal{ - Index: nextIndex, - Validator: uint64(nextIndex), - Address: common.BigToAddress(big.NewInt(nextAccount)), - Amount: big.NewInt(1), - } - nextWithdrawals = append(nextWithdrawals, nextWithdrawal) - withdrawnAccounts[common.BigToAddress(big.NewInt(nextAccount))] = true - nextIndex++ - nextAccount = (((nextAccount - startAccount) + 1) % differentAccounts) + startAccount - } - t.CLMock.NextWithdrawals = nextWithdrawals - ws.WithdrawalsHistory[t.CLMock.LatestHeader.Number.Uint64()+1] = nextWithdrawals + t.CLMock.NextWithdrawals, nextIndex = ws.GenerateWithdrawalsForBlock(nextIndex, startAccount) + ws.WithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals }, OnGetPayload: func() { - // Send new payload with `withdrawals=null` and expect error + // TODO: Send new payload with `withdrawals=null` and expect error }, OnNewPayloadBroadcast: func() { // Check withdrawal addresses and verify withdrawal balances @@ -422,8 +503,7 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { // Check the correct withdrawal root on past blocks r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) var expectedWithdrawalsRoot *common.Hash = nil - t.Logf("INFO (%s): firstWithdrawalBlock=%d, block=%d", t.TestName, firstWithdrawalBlock, block) - if block >= firstWithdrawalBlock { + if block >= uint64(ws.WithdrawalsForkHeight) { calcWithdrawalsRoot := types.DeriveSha( ws.WithdrawalsHistory.GetWithdrawals(block), trie.NewStackTrie(nil), @@ -453,7 +533,6 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { // Spawn a secondary client which will need to sync to the primary client secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles, t.Engine) - if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) } @@ -494,6 +573,219 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { // withdrawals block. type WithdrawalsReorgSpec struct { WithdrawalsBaseSpec - ReOrgOriginBlockHeight uint64 // Height of the block where the re-org will happen - ReOrgDestBlockHeight uint64 // Height of the block to which the re-org will rollback + + ReOrgDepth int // Depth of the block re-org + ReOrgViaSync bool // Whether the client should fetch the sidechain from the secondary client + SidechainBlockTimestampIncrease int +} + +func (ws WithdrawalsReorgSpec) ReOrgHeight() uint64 { + if ws.ReOrgDepth > ws.GetTotalPayloadCount() { + panic("invalid payload/re-org configuration") + } + return uint64(ws.GetTotalPayloadCount() - ws.ReOrgDepth + 1) +} + +func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { + // Create the withdrawals history object + ws.WithdrawalsHistory = make(WithdrawalsHistory) + + t.CLMock.WaitForTTD() + + // Spawn a secondary client which will produce the sidechain + secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles, t.Engine) + if err != nil { + t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) + } + secondaryEngineTest := test.NewTestEngineClient(t, secondaryEngine) + t.CLMock.AddEngineClient(secondaryEngine) + + if ws.ReOrgDepth > ws.WithdrawalsBlockCount { + // TODO: We are doing a re-org to pre-withdrawals state. + panic("Reorg depth to before withdrawals is not yet supported") + } else { + // We are simply doing a re-org afterwithdrawals had already happened + // First reach withdrawals fork + t.CLMock.ProduceBlocks(ws.GetPreWithdrawalsBlockCount(), clmock.BlockProcessCallbacks{}) + + } + + var ( + canonicalStartAccount = big.NewInt(0x1000) + canonicalNextIndex = uint64(0) + sidechainStartAccount = new(big.Int).SetBit(common.Big0, 160, 1) + sidechainNextIndex = uint64(0) + sidechainWithdrawalsHistory = make(WithdrawalsHistory) + sidechain = make(map[uint64]*beacon.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(ws.GetPreWithdrawalsBlockCount()+ws.WithdrawalsBlockCount, clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + if t.CLMock.CurrentPayloadNumber >= uint64(ws.WithdrawalsForkHeight) { + // Send some withdrawals + t.CLMock.NextWithdrawals, canonicalNextIndex = ws.GenerateWithdrawalsForBlock(canonicalNextIndex, canonicalStartAccount) + ws.WithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals + if t.CLMock.CurrentPayloadNumber >= ws.ReOrgHeight() { + // We also need to generate withdrawals for the sidechain + sidechainWithdrawalsHistory[t.CLMock.CurrentPayloadNumber], sidechainNextIndex = ws.GenerateWithdrawalsForBlock(sidechainNextIndex, sidechainStartAccount) + } else { + sidechainWithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals + sidechainNextIndex = canonicalNextIndex + } + } + }, + OnRequestNextPayload: func() { + if t.CLMock.CurrentPayloadNumber >= ws.ReOrgHeight() { + // Also request a payload from the sidechain + fcU := beacon.ForkchoiceStateV1{ + HeadBlockHash: t.CLMock.LatestForkchoice.HeadBlockHash, + } + + if t.CLMock.CurrentPayloadNumber > ws.ReOrgHeight() { + if lastSidePayload, ok := sidechain[t.CLMock.CurrentPayloadNumber-1]; !ok { + panic("sidechain payload not found") + } else { + fcU.HeadBlockHash = lastSidePayload.BlockHash + } + } + + t.Logf("INFO (%s): Requesting sidechain payload %d", t.TestName, t.CLMock.CurrentPayloadNumber) + + var version int + pAttributes := beacon.PayloadAttributes{ + Timestamp: t.CLMock.LatestPayloadAttributes.Timestamp, + Random: t.CLMock.LatestPayloadAttributes.Random, + SuggestedFeeRecipient: t.CLMock.LatestPayloadAttributes.SuggestedFeeRecipient, + } + if t.CLMock.CurrentPayloadNumber >= uint64(ws.WithdrawalsForkHeight) { + // Withdrawals + version = 2 + pAttributes.Withdrawals = sidechainWithdrawalsHistory[t.CLMock.CurrentPayloadNumber] + } else { + // No withdrawals + version = 1 + } + r := secondaryEngineTest.TestEngineForkchoiceUpdated(&fcU, &pAttributes, version) + r.ExpectNoError() + r.ExpectPayloadStatus(test.Valid) + if r.Response.PayloadID == nil { + t.Fatalf("FAIL (%s): Unable to get a payload ID on the sidechain", t.TestName) + } + sidechainPayloadId = r.Response.PayloadID + } + }, + OnGetPayload: func() { + var ( + version int + payload *beacon.ExecutableData + ) + if t.CLMock.CurrentPayloadNumber >= uint64(ws.WithdrawalsForkHeight) { + version = 2 + } else { + version = 1 + } + if t.CLMock.LatestPayloadBuilt.Number >= ws.ReOrgHeight() { + // This payload is built on top of a sidechain payload, get the payload for that too + r := secondaryEngineTest.TestEngineGetPayload(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.TestEngineNewPayload(payload, version) + r.ExpectStatus(test.Valid) + p := secondaryEngineTest.TestEngineForkchoiceUpdated( + &beacon.ForkchoiceStateV1{ + HeadBlockHash: payload.BlockHash, + }, + nil, + version, + ) + p.ExpectPayloadStatus(test.Valid) + }, + }) + + latestPayloadHeight := t.CLMock.LatestExecutedPayload.Number + + // Check the withdrawals on the latest + ws.WithdrawalsHistory.VerifyWithdrawals( + latestPayloadHeight, + nil, + t.TestEngine, + ) + + if ws.ReOrgViaSync { + // Send latest sidechain payload as NewPayload + FCU and wait for sync + loop: + for { + r := t.TestEngine.TestEngineNewPayloadV2(sidechain[latestPayloadHeight]) + r.ExpectNoError() + p := t.TestEngine.TestEngineForkchoiceUpdatedV2( + &beacon.ForkchoiceStateV1{ + HeadBlockHash: sidechain[latestPayloadHeight].BlockHash, + }, + nil, + ) + p.ExpectNoError() + select { + case <-t.TimeoutContext.Done(): + t.Fatalf("FAIL (%s): Timeout waiting for sync", t.TestName) + case <-time.After(time.Second): + b := t.TestEngine.TestBlockByNumber(nil) + if b.Block.Hash() == sidechain[latestPayloadHeight].BlockHash { + // sync successful + break loop + } + } + } + } else { + // Send all payloads one by one to the primary client + for payloadNumber := latestPayloadHeight - uint64(ws.ReOrgDepth) + 1; payloadNumber <= latestPayloadHeight; payloadNumber++ { + payload, ok := sidechain[payloadNumber] + if !ok { + t.Fatalf("FAIL (%s): Invalid payload %d requested.", t.TestName, payloadNumber) + } + var version int + if payloadNumber >= uint64(ws.WithdrawalsForkHeight) { + version = 2 + } else { + version = 1 + } + t.Logf("INFO (%s): Sending sidechain payload %d, hash=%s, parent=%s", t.TestName, payloadNumber, payload.BlockHash, payload.ParentHash) + r := t.TestEngine.TestEngineNewPayload(payload, version) + r.ExpectStatusEither(test.Valid, test.Accepted) + p := t.TestEngine.TestEngineForkchoiceUpdated( + &beacon.ForkchoiceStateV1{ + HeadBlockHash: payload.BlockHash, + }, + nil, + version, + ) + p.ExpectPayloadStatus(test.Valid) + } + } + + // Verify withdrawals changed + sidechainWithdrawalsHistory.VerifyWithdrawals( + latestPayloadHeight, + 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.WithdrawalsHistory.VerifyWithdrawals( + uint64(ws.WithdrawalsForkHeight)-1, + nil, + t.TestEngine, + ) + } diff --git a/simulators/ethereum/engine/test/expect.go b/simulators/ethereum/engine/test/expect.go index ba14435d39..f21439c1d7 100644 --- a/simulators/ethereum/engine/test/expect.go +++ b/simulators/ethereum/engine/test/expect.go @@ -2,6 +2,7 @@ package test import ( "context" + "encoding/json" "fmt" "math/big" "runtime" @@ -104,6 +105,13 @@ func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV2(fcState *api.Forkchoi } } +func (tec *TestEngineClient) TestEngineForkchoiceUpdated(fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes, version int) *ForkchoiceResponseExpectObject { + if version == 2 { + return tec.TestEngineForkchoiceUpdatedV2(fcState, pAttributes) + } + return tec.TestEngineForkchoiceUpdatedV1(fcState, pAttributes) +} + func (exp *ForkchoiceResponseExpectObject) ExpectNoError() { if exp.Error != nil { exp.Fatalf("FAIL (%s): Unexpected error on EngineForkchoiceUpdatedV%d: %v, expected=", exp.TestName, exp.Version, exp.Error) @@ -166,6 +174,7 @@ func (exp *ForkchoiceResponseExpectObject) ExpectPayloadID(pid *api.PayloadID) { type NewPayloadResponseExpectObject struct { *ExpectEnv + Payload *api.ExecutableData Status api.PayloadStatusV1 Version int Error error @@ -177,6 +186,7 @@ func (tec *TestEngineClient) TestEngineNewPayloadV1(payload *api.ExecutableData) status, err := tec.Engine.NewPayloadV1(ctx, payload) return &NewPayloadResponseExpectObject{ ExpectEnv: &ExpectEnv{tec.Env}, + Payload: payload, Status: status, Version: 1, Error: err, @@ -189,28 +199,41 @@ func (tec *TestEngineClient) TestEngineNewPayloadV2(payload *api.ExecutableData) status, err := tec.Engine.NewPayloadV2(ctx, payload) return &NewPayloadResponseExpectObject{ ExpectEnv: &ExpectEnv{tec.Env}, + Payload: payload, Status: status, Version: 2, Error: err, } } +func (tec *TestEngineClient) TestEngineNewPayload(payload *api.ExecutableData, version int) *NewPayloadResponseExpectObject { + if version == 2 { + return tec.TestEngineNewPayloadV2(payload) + } + return tec.TestEngineNewPayloadV1(payload) +} + +func (exp *NewPayloadResponseExpectObject) PayloadJson() string { + jsonPayload, _ := json.MarshalIndent(exp.Payload, "", " ") + return string(jsonPayload) +} + func (exp *NewPayloadResponseExpectObject) ExpectNoError() { if exp.Error != nil { - exp.Fatalf("FAIL (%s): Expected no error on EngineNewPayloadV%d: error=%v", exp.TestName, exp.Version, exp.Error) + exp.Fatalf("FAIL (%s): Expected no error on EngineNewPayloadV%d: error=%v, payload=%s", exp.TestName, exp.Version, exp.Error, exp.PayloadJson()) } } func (exp *NewPayloadResponseExpectObject) ExpectError() { if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV%d: status=%v", exp.TestName, exp.Version, exp.Status) + exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV%d: status=%v, payload=%s", exp.TestName, exp.Version, exp.Status, exp.PayloadJson()) } } func (exp *NewPayloadResponseExpectObject) ExpectErrorCode(code int) { // TODO: Actually check error code if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV%d: status=%v", exp.TestName, exp.Version, exp.Status) + exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV%d: status=%v, payload=%s", exp.TestName, exp.Version, exp.Status, exp.PayloadJson()) } } @@ -223,7 +246,7 @@ func (exp *NewPayloadResponseExpectObject) ExpectNoValidationError() { func (exp *NewPayloadResponseExpectObject) ExpectStatus(ps PayloadStatus) { exp.ExpectNoError() if PayloadStatus(exp.Status.Status) != ps { - exp.Fatalf("FAIL (%s): Unexpected status response on EngineNewPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Status.Status, ps) + exp.Fatalf("FAIL (%s): Unexpected status response on EngineNewPayloadV%d: %v, expected=%v, payload=%s", exp.TestName, exp.Version, exp.Status.Status, ps, exp.PayloadJson()) } } @@ -235,7 +258,7 @@ func (exp *NewPayloadResponseExpectObject) ExpectStatusEither(statuses ...Payloa } } - exp.Fatalf("FAIL (%s): Unexpected status response on EngineNewPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Status.Status, strings.Join(StatusesToString(statuses), ",")) + exp.Fatalf("FAIL (%s): Unexpected status response on EngineNewPayloadV%d: %v, expected=%v, payload=%s", exp.TestName, exp.Version, exp.Status.Status, strings.Join(StatusesToString(statuses), ","), exp.PayloadJson()) } func (exp *NewPayloadResponseExpectObject) ExpectLatestValidHash(lvh *common.Hash) { @@ -275,6 +298,13 @@ func (tec *TestEngineClient) TestEngineGetPayloadV2(payloadID *api.PayloadID) *G } } +func (tec *TestEngineClient) TestEngineGetPayload(payloadID *api.PayloadID, version int) *GetPayloadResponseExpectObject { + if version == 2 { + return tec.TestEngineGetPayloadV2(payloadID) + } + return tec.TestEngineGetPayloadV1(payloadID) +} + func (exp *GetPayloadResponseExpectObject) ExpectNoError() { if exp.Error != nil { exp.Fatalf("FAIL (%s): Expected no error on EngineGetPayloadV1: error=%v", exp.TestName, exp.Error) From 2a6971610baef4e626a4575aa69cdefe94315191 Mon Sep 17 00:00:00 2001 From: marioevz Date: Wed, 23 Nov 2022 17:47:14 -0600 Subject: [PATCH 16/49] simulators/ethereum/engine: More withdrawals tests --- .../engine/suites/withdrawals/tests.go | 65 +++++++++++++------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 1796a3ff3f..97951184cb 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -187,9 +187,9 @@ var Tests = []test.SpecInterface{ WithdrawalsReorgSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ - Name: "Withdrawals Fork on Block 1 - 1 Block Deep Re-Org", + Name: "Withdrawals Fork on Block 1 - 1 Block Re-Org", About: ` - Tests a simple 1 block deep re-org + Tests a simple 1 block re-org `, SlotsToSafe: big.NewInt(32), SlotsToFinalized: big.NewInt(64), @@ -198,14 +198,15 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 16, WithdrawalsPerBlock: 16, }, - ReOrgDepth: 1, + ReOrgDepth: 1, + ReOrgViaSync: false, }, WithdrawalsReorgSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ - Name: "Withdrawals Fork on Block 1 - 8 Block Deep Re-Org NewPayload", + Name: "Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload", About: ` - Tests a 8 block deep re-org using NewPayload + Tests a 8 block re-org using NewPayload `, SlotsToSafe: big.NewInt(32), SlotsToFinalized: big.NewInt(64), @@ -214,14 +215,15 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 16, WithdrawalsPerBlock: 16, }, - ReOrgDepth: 8, + ReOrgDepth: 8, + ReOrgViaSync: false, }, WithdrawalsReorgSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ - Name: "Withdrawals Fork on Block 1 - 8 Block Deep Re-Org, Sync", + Name: "Withdrawals Fork on Block 1 - 8 Block Re-Org, Sync", About: ` - Tests a 8 block deep re-org using sync + Tests a 8 block re-org using sync `, SlotsToSafe: big.NewInt(32), SlotsToFinalized: big.NewInt(64), @@ -233,7 +235,42 @@ var Tests = []test.SpecInterface{ ReOrgDepth: 8, ReOrgViaSync: true, }, - // REORG WHERE THE FORK HAPPENS ON A DIFFERENT BLOCK HEIGHT!!!! + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Block 8 - 10 Block Re-Org NewPayload", + About: ` + Tests a 8 block re-org using sync + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 8, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 8, + WithdrawalsPerBlock: 128, + }, + ReOrgDepth: 10, + ReOrgViaSync: false, + }, + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Block 8 - 10 Block Re-Org Sync", + About: ` + Tests a 8 block re-org using sync + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 8, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 8, + WithdrawalsPerBlock: 128, + }, + ReOrgDepth: 10, + ReOrgViaSync: true, + }, + // TODO: REORG WHERE THE FORK HAPPENS ON A DIFFERENT BLOCK HEIGHT + // TODO: REORG SYNC WHERE SYNCED BLOCKS HAVE WITHDRAWALS BEFORE TIME } // Helper structure used to keep history of the amounts withdrawn to each test account. @@ -600,16 +637,6 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { secondaryEngineTest := test.NewTestEngineClient(t, secondaryEngine) t.CLMock.AddEngineClient(secondaryEngine) - if ws.ReOrgDepth > ws.WithdrawalsBlockCount { - // TODO: We are doing a re-org to pre-withdrawals state. - panic("Reorg depth to before withdrawals is not yet supported") - } else { - // We are simply doing a re-org afterwithdrawals had already happened - // First reach withdrawals fork - t.CLMock.ProduceBlocks(ws.GetPreWithdrawalsBlockCount(), clmock.BlockProcessCallbacks{}) - - } - var ( canonicalStartAccount = big.NewInt(0x1000) canonicalNextIndex = uint64(0) From 197db62ff541bf9102f8907ec9a31160f699f257 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 24 Nov 2022 13:09:31 -0600 Subject: [PATCH 17/49] simulators/ethereum/engine: Variable timestamp increments on CLMock --- simulators/ethereum/engine/clmock/clmock.go | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/simulators/ethereum/engine/clmock/clmock.go b/simulators/ethereum/engine/clmock/clmock.go index e17b382877..1e1cadb75a 100644 --- a/simulators/ethereum/engine/clmock/clmock.go +++ b/simulators/ethereum/engine/clmock/clmock.go @@ -42,6 +42,9 @@ type CLMocker struct { // Wait time before attempting to get the payload PayloadProductionClientDelay time.Duration + // Block production related + BlockTimestampIncrement *big.Int + // Block Production State NextBlockProducer client.EngineClient NextFeeRecipient common.Address @@ -218,6 +221,24 @@ func (cl *CLMocker) IsBlockPoS(bn *big.Int) bool { return true } +// Return the per-block timestamp value increment +func (cl *CLMocker) GetTimestampIncrement() uint64 { + if cl.BlockTimestampIncrement == nil { + return 1 + } + return cl.BlockTimestampIncrement.Uint64() +} + +// Returns the timestamp value to be included in the next payload attributes +func (cl *CLMocker) GetNextBlockTimestamp() uint64 { + if cl.FirstPoSBlockNumber == nil && cl.TransitionPayloadTimestamp != nil { + // We are producing the transition payload and there's a value specified + // for this specific payload + return cl.TransitionPayloadTimestamp.Uint64() + } + return cl.LatestHeader.Time + cl.GetTimestampIncrement() +} + // Picks the next payload producer from the set of clients registered func (cl *CLMocker) pickNextPayloadProducer() { if len(cl.EngineClients) == 0 { @@ -269,13 +290,7 @@ func (cl *CLMocker) RequestNextPayload() { cl.LatestPayloadAttributes = api.PayloadAttributes{ Random: nextPrevRandao, SuggestedFeeRecipient: cl.NextFeeRecipient, - } - - if cl.FirstPoSBlockNumber == nil && cl.TransitionPayloadTimestamp != nil { - // We are producing the transition payload - cl.LatestPayloadAttributes.Timestamp = cl.TransitionPayloadTimestamp.Uint64() - } else { - cl.LatestPayloadAttributes.Timestamp = cl.LatestHeader.Time + 1 + Timestamp: cl.GetNextBlockTimestamp(), } if isShanghai(cl.LatestPayloadAttributes.Timestamp, cl.ShanghaiTimestamp) && cl.NextWithdrawals != nil { From 306df1d54b4f5d321f19a1b79f006625e7443a83 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 24 Nov 2022 13:23:37 -0600 Subject: [PATCH 18/49] simulators/ethereum/engine: Withdrawals reorg changes --- .../engine/suites/withdrawals/tests.go | 85 ++++++++++++++++--- simulators/ethereum/engine/test/env.go | 4 + simulators/ethereum/engine/test/expect.go | 7 ++ simulators/ethereum/engine/test/spec.go | 6 ++ 4 files changed, 89 insertions(+), 13 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 97951184cb..9f02bca108 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -65,6 +65,7 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 1, WithdrawalsPerBlock: 16, }, + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw to a single account", @@ -77,6 +78,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 64, WithdrawableAccountCount: 1, }, + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw to two accounts", @@ -93,6 +95,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 64, WithdrawableAccountCount: 2, }, + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw many accounts", @@ -107,6 +110,21 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 1024, WithdrawableAccountCount: 1024, }, + + WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdraw zero amount", + About: ` + Make multiple withdrawals with amount==0. + `, + }, + WithdrawalsForkHeight: 1, + WithdrawalsBlockCount: 1, + WithdrawalsPerBlock: 64, + WithdrawableAccountCount: 2, + WithdrawAmounts: []*big.Int{common.Big0, common.Big1}, + }, + // Sync Tests WithdrawalsSyncSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ @@ -355,27 +373,41 @@ func (wh WithdrawalsHistory) Copy() WithdrawalsHistory { // on genesis or afterwards. type WithdrawalsBaseSpec struct { test.Spec + BlockTimestampIncrements int64 // Timestamp increments per block throughout the test WithdrawalsForkHeight int64 // Withdrawals activation fork height WithdrawalsBlockCount int // Number of blocks on and after withdrawals fork activation WithdrawalsPerBlock int // Number of withdrawals per block WithdrawableAccountCount uint64 // Number of accounts to withdraw to (round-robin) WithdrawalsHistory WithdrawalsHistory // Internal withdrawals history that keeps track of all withdrawals + WithdrawAmounts []*big.Int // Amounts of withdrawn wei on each withdrawal (round-robin) SkipBaseVerifications bool // For code reuse of the base spec procedure } +// Get the per-block timestamp increments configured for this test +func (ws WithdrawalsBaseSpec) GetBlockTimestampIncrements() int64 { + if ws.BlockTimestampIncrements == 0 { + return 1 + } + return ws.BlockTimestampIncrements +} + // Calculates Shanghai fork timestamp given the amount of blocks that need to be // produced beforehand (CLMock produces 1 block each second). -func (ws WithdrawalsBaseSpec) WithdrawalsTimestamp() int64 { - return globals.GenesisTimestamp + ws.WithdrawalsForkHeight +func (ws WithdrawalsBaseSpec) GetWithdrawalsTimestamp() int64 { + return globals.GenesisTimestamp + (ws.WithdrawalsForkHeight * ws.GetBlockTimestampIncrements()) } // Generates the fork config, including withdrawals fork timestamp. func (ws WithdrawalsBaseSpec) GetForkConfig() test.ForkConfig { return test.ForkConfig{ - ShanghaiTimestamp: big.NewInt(ws.WithdrawalsTimestamp()), + ShanghaiTimestamp: big.NewInt(ws.GetWithdrawalsTimestamp()), } } +func (ws WithdrawalsBaseSpec) ConfigureCLMock(cl *clmock.CLMocker) { + cl.BlockTimestampIncrement = big.NewInt(ws.GetBlockTimestampIncrements()) +} + // Number of blocks to be produced (not counting genesis) before withdrawals // fork. func (ws WithdrawalsBaseSpec) GetPreWithdrawalsBlockCount() int { @@ -402,6 +434,12 @@ func (ws WithdrawalsBaseSpec) GetWithdrawableAccountCount() uint64 { // Generates a list of withdrawals based on current configuration func (ws WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, startAccount *big.Int) (types.Withdrawals, uint64) { differentAccounts := ws.GetWithdrawableAccountCount() + withdrawAmounts := ws.WithdrawAmounts + if withdrawAmounts == nil { + withdrawAmounts = []*big.Int{ + big.NewInt(1), + } + } nextWithdrawals := make(types.Withdrawals, 0) for i := 0; i < ws.WithdrawalsPerBlock; i++ { @@ -411,7 +449,7 @@ func (ws WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, star Index: nextIndex, Validator: uint64(nextIndex), Address: common.BigToAddress(nextAccount), - Amount: big.NewInt(1), + Amount: withdrawAmounts[int(nextIndex)%len(withdrawAmounts)], } nextWithdrawals = append(nextWithdrawals, nextWithdrawal) nextIndex++ @@ -419,6 +457,7 @@ func (ws WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, star return nextWithdrawals, nextIndex } +// Base test case execution procedure for withdrawals func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { // Create the withdrawals history object ws.WithdrawalsHistory = make(WithdrawalsHistory) @@ -426,7 +465,7 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { t.CLMock.WaitForTTD() // Check if we have pre-Shanghai blocks - if ws.WithdrawalsTimestamp() > globals.GenesisTimestamp { + if ws.GetWithdrawalsTimestamp() > globals.GenesisTimestamp { // Check `latest` during all pre-shanghai blocks, none should // contain `withdrawalsRoot`, including genesis. @@ -611,18 +650,38 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { type WithdrawalsReorgSpec struct { WithdrawalsBaseSpec - ReOrgDepth int // Depth of the block re-org - ReOrgViaSync bool // Whether the client should fetch the sidechain from the secondary client - SidechainBlockTimestampIncrease int + ReOrgDepth int // Depth of the block re-org + ReOrgViaSync bool // Whether the client should fetch the sidechain from the secondary client + SidechainBlockTimestampIncrements int64 } -func (ws WithdrawalsReorgSpec) ReOrgHeight() uint64 { +func (ws WithdrawalsReorgSpec) GetSidechainSplitHeight() uint64 { if ws.ReOrgDepth > ws.GetTotalPayloadCount() { panic("invalid payload/re-org configuration") } return uint64(ws.GetTotalPayloadCount() - ws.ReOrgDepth + 1) } +func (ws WithdrawalsReorgSpec) GetSidechainBlockTimestampIncrements() int64 { + if ws.SidechainBlockTimestampIncrements == 0 { + return ws.GetBlockTimestampIncrements() + } + return ws.SidechainBlockTimestampIncrements +} + +func (ws WithdrawalsReorgSpec) GetSidechainWithdrawalsForkHeight() int64 { + if ws.GetSidechainBlockTimestampIncrements() != ws.GetBlockTimestampIncrements() { + // Block timestamp increments in both chains are different so need to calculate different heights, only if split happens before fork + if ws.GetSidechainSplitHeight() < uint64(ws.WithdrawalsForkHeight) { + // We need to calculate the height of the fork on the sidechain + sidechainSplitBlockTimestamp := globals.GenesisTimestamp + (int64(ws.GetSidechainSplitHeight()) * ws.GetBlockTimestampIncrements()) + return ((ws.GetWithdrawalsTimestamp() - sidechainSplitBlockTimestamp) / ws.SidechainBlockTimestampIncrements) + int64(ws.GetSidechainSplitHeight()) + + } + } + return ws.WithdrawalsForkHeight +} + func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { // Create the withdrawals history object ws.WithdrawalsHistory = make(WithdrawalsHistory) @@ -656,7 +715,7 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { // Send some withdrawals t.CLMock.NextWithdrawals, canonicalNextIndex = ws.GenerateWithdrawalsForBlock(canonicalNextIndex, canonicalStartAccount) ws.WithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals - if t.CLMock.CurrentPayloadNumber >= ws.ReOrgHeight() { + if t.CLMock.CurrentPayloadNumber >= ws.GetSidechainSplitHeight() { // We also need to generate withdrawals for the sidechain sidechainWithdrawalsHistory[t.CLMock.CurrentPayloadNumber], sidechainNextIndex = ws.GenerateWithdrawalsForBlock(sidechainNextIndex, sidechainStartAccount) } else { @@ -666,13 +725,13 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { } }, OnRequestNextPayload: func() { - if t.CLMock.CurrentPayloadNumber >= ws.ReOrgHeight() { + 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.ReOrgHeight() { + if t.CLMock.CurrentPayloadNumber > ws.GetSidechainSplitHeight() { if lastSidePayload, ok := sidechain[t.CLMock.CurrentPayloadNumber-1]; !ok { panic("sidechain payload not found") } else { @@ -715,7 +774,7 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { } else { version = 1 } - if t.CLMock.LatestPayloadBuilt.Number >= ws.ReOrgHeight() { + if t.CLMock.LatestPayloadBuilt.Number >= ws.GetSidechainSplitHeight() { // This payload is built on top of a sidechain payload, get the payload for that too r := secondaryEngineTest.TestEngineGetPayload(sidechainPayloadId, version) r.ExpectNoError() diff --git a/simulators/ethereum/engine/test/env.go b/simulators/ethereum/engine/test/env.go index 5657d69178..b28c7a6b23 100644 --- a/simulators/ethereum/engine/test/env.go +++ b/simulators/ethereum/engine/test/env.go @@ -54,6 +54,10 @@ func Run(testSpec SpecInterface, ttd *big.Int, timeout time.Duration, t *hivesim consensusConfig.SlotsToFinalized, big.NewInt(consensusConfig.SafeSlotsToImportOptimistically), testSpec.GetForkConfig().ShanghaiTimestamp) + + // Send the CLMocker for configuration by the spec, if any. + testSpec.ConfigureCLMock(clMocker) + // Defer closing all clients defer func() { clMocker.CloseClients() diff --git a/simulators/ethereum/engine/test/expect.go b/simulators/ethereum/engine/test/expect.go index f21439c1d7..66272ed6e9 100644 --- a/simulators/ethereum/engine/test/expect.go +++ b/simulators/ethereum/engine/test/expect.go @@ -553,6 +553,13 @@ func (exp *BalanceResponseExpectObject) ExpectNoError() { } func (exp *BalanceResponseExpectObject) ExpectBalanceEqual(expBalance *big.Int) { + exp.Logf("INFO (%s): Testing balance for account %s on block %d: actual=%d, expected=%d", + exp.TestName, + exp.Account, + exp.Block, + exp.Balance, + expBalance, + ) exp.ExpectNoError() if ((expBalance == nil || exp.Balance == nil) && expBalance != exp.Balance) || (expBalance != nil && exp.Balance != nil && expBalance.Cmp(exp.Balance) != 0) { diff --git a/simulators/ethereum/engine/test/spec.go b/simulators/ethereum/engine/test/spec.go index c325c4e97d..4c93ed4900 100644 --- a/simulators/ethereum/engine/test/spec.go +++ b/simulators/ethereum/engine/test/spec.go @@ -3,6 +3,7 @@ package test import ( "math/big" + "github.com/ethereum/hive/simulators/ethereum/engine/clmock" "github.com/ethereum/hive/simulators/ethereum/engine/helper" ) @@ -19,6 +20,7 @@ type ConsensusConfig struct { type SpecInterface interface { Execute(*Env) + ConfigureCLMock(*clmock.CLMocker) GetAbout() string GetConsensusConfig() ConsensusConfig GetChainFile() string @@ -80,6 +82,10 @@ func (s Spec) Execute(env *Env) { s.Run(env) } +func (s Spec) ConfigureCLMock(*clmock.CLMocker) { + // No-op +} + func (s Spec) GetAbout() string { return s.About } From 7e7c461da2b8628ca7408f53f3a59aeced91247b Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 25 Nov 2022 12:18:09 -0600 Subject: [PATCH 19/49] simulators/ethereum/engine: withdrawals reorg tests --- .../suites/withdrawals/spec_unit_test.go | 145 ++++++++ .../engine/suites/withdrawals/tests.go | 310 +++++++++++++----- 2 files changed, 377 insertions(+), 78 deletions(-) create mode 100644 simulators/ethereum/engine/suites/withdrawals/spec_unit_test.go diff --git a/simulators/ethereum/engine/suites/withdrawals/spec_unit_test.go b/simulators/ethereum/engine/suites/withdrawals/spec_unit_test.go new file mode 100644 index 0000000000..07eec23c97 --- /dev/null +++ b/simulators/ethereum/engine/suites/withdrawals/spec_unit_test.go @@ -0,0 +1,145 @@ +package suite_withdrawals + +import ( + "testing" + + "github.com/ethereum/hive/simulators/ethereum/engine/globals" +) + +type BaseSpecExpected struct { + Spec WithdrawalsBaseSpec + ExpectedBlockTimeIncrements uint64 + ExpectedWithdrawalsTimestamp uint64 + ExpectedPreWithdrawalsBlockCount uint64 + ExpectedTotalPayloadCount uint64 +} + +var baseSpecTestCases = []BaseSpecExpected{ + { + Spec: WithdrawalsBaseSpec{}, + ExpectedBlockTimeIncrements: 1, + ExpectedWithdrawalsTimestamp: uint64(globals.GenesisTimestamp), + ExpectedPreWithdrawalsBlockCount: 0, + ExpectedTotalPayloadCount: 0, + }, + { + Spec: WithdrawalsBaseSpec{ + WithdrawalsBlockCount: 1, + }, + ExpectedBlockTimeIncrements: 1, + ExpectedWithdrawalsTimestamp: uint64(globals.GenesisTimestamp), + ExpectedPreWithdrawalsBlockCount: 0, + ExpectedTotalPayloadCount: 1, + }, +} + +func TestBaseSpecFunctions(t *testing.T) { + for i, tc := range baseSpecTestCases { + spec := tc.Spec + if spec.GetBlockTimeIncrements() != tc.ExpectedBlockTimeIncrements { + t.Fatalf("tc %d: unexpected block timestamp increments, expected=%d, got=%d", i, tc.ExpectedBlockTimeIncrements, spec.GetBlockTimeIncrements()) + } + if spec.GetWithdrawalsForkTime() != tc.ExpectedWithdrawalsTimestamp { + t.Fatalf("tc %d: unexpected withdrawals timestamp, expected=%d, got=%d", i, tc.ExpectedWithdrawalsTimestamp, spec.GetWithdrawalsForkTime()) + } + if spec.GetPreWithdrawalsBlockCount() != tc.ExpectedPreWithdrawalsBlockCount { + t.Fatalf("tc %d: unexpected pre-withdrawals block count, expected=%d, got=%d", i, tc.ExpectedPreWithdrawalsBlockCount, spec.GetPreWithdrawalsBlockCount()) + } + if spec.GetTotalPayloadCount() != tc.ExpectedTotalPayloadCount { + t.Fatalf("tc %d: unexpected total payload count, expected=%d, got=%d", i, tc.ExpectedTotalPayloadCount, spec.GetTotalPayloadCount()) + } + } +} + +type ReOrgSpecExpected struct { + Spec WithdrawalsReorgSpec + ExpectedSidechainSplitHeight uint64 + ExpectedSidechainBlockTimeIncrements uint64 + ExpectedSidechainWithdrawalsForkHeight uint64 +} + +var reorgSpecTestCases = []ReOrgSpecExpected{ + { + Spec: WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + WithdrawalsForkHeight: 1, + WithdrawalsBlockCount: 16, + }, + ReOrgBlockCount: 1, + }, + ExpectedSidechainSplitHeight: 16, + ExpectedSidechainBlockTimeIncrements: 1, + ExpectedSidechainWithdrawalsForkHeight: 1, + }, + { + Spec: WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + TimeIncrements: 12, + WithdrawalsForkHeight: 4, + WithdrawalsBlockCount: 1, + }, + ReOrgBlockCount: 1, + SidechainTimeIncrements: 1, + }, + ExpectedSidechainSplitHeight: 4, + ExpectedSidechainBlockTimeIncrements: 1, + ExpectedSidechainWithdrawalsForkHeight: 15, + }, + { + Spec: WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + TimeIncrements: 1, + WithdrawalsForkHeight: 4, + WithdrawalsBlockCount: 4, + }, + ReOrgBlockCount: 6, + SidechainTimeIncrements: 4, + }, + ExpectedSidechainSplitHeight: 2, + ExpectedSidechainBlockTimeIncrements: 4, + ExpectedSidechainWithdrawalsForkHeight: 2, + }, + { + Spec: WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + TimeIncrements: 1, + WithdrawalsForkHeight: 8, + WithdrawalsBlockCount: 8, + }, + ReOrgBlockCount: 10, + SidechainTimeIncrements: 2, + }, + ExpectedSidechainSplitHeight: 6, + ExpectedSidechainBlockTimeIncrements: 2, + ExpectedSidechainWithdrawalsForkHeight: 7, + }, + { + Spec: WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + TimeIncrements: 2, + WithdrawalsForkHeight: 8, + WithdrawalsBlockCount: 8, + }, + ReOrgBlockCount: 10, + SidechainTimeIncrements: 1, + }, + ExpectedSidechainSplitHeight: 6, + ExpectedSidechainBlockTimeIncrements: 1, + ExpectedSidechainWithdrawalsForkHeight: 11, + }, +} + +func TestReorgSpecFunctions(t *testing.T) { + for i, tc := range reorgSpecTestCases { + spec := tc.Spec + if spec.GetSidechainSplitHeight() != tc.ExpectedSidechainSplitHeight { + t.Fatalf("tc %d: unexpected sidechain split height, expected=%d, got=%d", i, tc.ExpectedSidechainSplitHeight, spec.GetSidechainSplitHeight()) + } + if spec.GetSidechainBlockTimeIncrements() != tc.ExpectedSidechainBlockTimeIncrements { + t.Fatalf("tc %d: unexpected sidechain block timestamp increments, expected=%d, got=%d", i, tc.ExpectedSidechainBlockTimeIncrements, spec.GetSidechainBlockTimeIncrements()) + } + if spec.GetSidechainWithdrawalsForkHeight() != tc.ExpectedSidechainWithdrawalsForkHeight { + t.Fatalf("tc %d: unexpected sidechain withdrawals fork height, expected=%d, got=%d", i, tc.ExpectedSidechainWithdrawalsForkHeight, spec.GetSidechainWithdrawalsForkHeight()) + } + } +} diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 9f02bca108..e8c6c6907b 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -216,8 +216,8 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 16, WithdrawalsPerBlock: 16, }, - ReOrgDepth: 1, - ReOrgViaSync: false, + ReOrgBlockCount: 1, + ReOrgViaSync: false, }, WithdrawalsReorgSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ @@ -225,6 +225,7 @@ var Tests = []test.SpecInterface{ Name: "Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload", About: ` Tests a 8 block re-org using NewPayload + Re-org does not change withdrawals fork height `, SlotsToSafe: big.NewInt(32), SlotsToFinalized: big.NewInt(64), @@ -233,15 +234,16 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 16, WithdrawalsPerBlock: 16, }, - ReOrgDepth: 8, - ReOrgViaSync: false, + ReOrgBlockCount: 8, + ReOrgViaSync: false, }, WithdrawalsReorgSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 1 - 8 Block Re-Org, Sync", About: ` - Tests a 8 block re-org using sync + Tests a 8 block re-org using NewPayload + Re-org does not change withdrawals fork height `, SlotsToSafe: big.NewInt(32), SlotsToFinalized: big.NewInt(64), @@ -250,15 +252,17 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 16, WithdrawalsPerBlock: 16, }, - ReOrgDepth: 8, - ReOrgViaSync: true, + ReOrgBlockCount: 8, + ReOrgViaSync: true, }, WithdrawalsReorgSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 8 - 10 Block Re-Org NewPayload", About: ` - Tests a 8 block re-org using sync + Tests a 10 block re-org using NewPayload + Re-org does not change withdrawals fork height, but changes + the payload at the height of the fork `, SlotsToSafe: big.NewInt(32), SlotsToFinalized: big.NewInt(64), @@ -267,15 +271,76 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 8, WithdrawalsPerBlock: 128, }, - ReOrgDepth: 10, - ReOrgViaSync: false, + ReOrgBlockCount: 10, + ReOrgViaSync: false, }, WithdrawalsReorgSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 8 - 10 Block Re-Org Sync", About: ` - Tests a 8 block re-org using sync + 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 + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 8, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 8, + WithdrawalsPerBlock: 128, + }, + ReOrgBlockCount: 10, + ReOrgViaSync: true, + }, + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org", + About: ` + Tests a 10 block re-org using NewPayload + Sidechain reaches withdrawals fork at a lower block height + than the canonical chain + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 8, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 8, + WithdrawalsPerBlock: 128, + }, + ReOrgBlockCount: 10, + ReOrgViaSync: false, + SidechainTimeIncrements: 2, + }, + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + 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 + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 8, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 8, + WithdrawalsPerBlock: 128, + }, + ReOrgBlockCount: 10, + ReOrgViaSync: true, + SidechainTimeIncrements: 2, + }, + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org", + About: ` + Tests a 10 block re-org using NewPayload + Sidechain reaches withdrawals fork at a higher block height + than the canonical chain `, SlotsToSafe: big.NewInt(32), SlotsToFinalized: big.NewInt(64), @@ -283,11 +348,33 @@ var Tests = []test.SpecInterface{ WithdrawalsForkHeight: 8, // Genesis is Pre-Withdrawals WithdrawalsBlockCount: 8, WithdrawalsPerBlock: 128, + TimeIncrements: 2, }, - ReOrgDepth: 10, - ReOrgViaSync: true, + ReOrgBlockCount: 10, + ReOrgViaSync: false, + SidechainTimeIncrements: 1, + }, + WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + 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 + `, + SlotsToSafe: big.NewInt(32), + SlotsToFinalized: big.NewInt(64), + }, + WithdrawalsForkHeight: 8, // Genesis is Pre-Withdrawals + WithdrawalsBlockCount: 8, + WithdrawalsPerBlock: 128, + TimeIncrements: 2, + }, + ReOrgBlockCount: 10, + ReOrgViaSync: true, + SidechainTimeIncrements: 1, }, - // TODO: REORG WHERE THE FORK HAPPENS ON A DIFFERENT BLOCK HEIGHT // TODO: REORG SYNC WHERE SYNCED BLOCKS HAVE WITHDRAWALS BEFORE TIME } @@ -299,7 +386,7 @@ type WithdrawalsHistory map[uint64]types.Withdrawals func (wh WithdrawalsHistory) GetExpectedAccountBalance(account common.Address, block uint64) *big.Int { balance := big.NewInt(0) for b := uint64(0); b <= block; b++ { - if withdrawals, ok := wh[b]; ok { + if withdrawals, ok := wh[b]; ok && withdrawals != nil { for _, withdrawal := range withdrawals { if withdrawal.Address == account { balance.Add(balance, withdrawal.Amount) @@ -313,7 +400,7 @@ func (wh WithdrawalsHistory) GetExpectedAccountBalance(account common.Address, b // Get a list of all addresses that were credited by withdrawals on a given block. func (wh WithdrawalsHistory) GetAddressesWithdrawnOnBlock(block uint64) []common.Address { addressMap := make(map[common.Address]bool) - if withdrawals, ok := wh[block]; ok { + if withdrawals, ok := wh[block]; ok && withdrawals != nil { for _, withdrawal := range withdrawals { addressMap[withdrawal.Address] = true } @@ -327,7 +414,7 @@ func (wh WithdrawalsHistory) GetAddressesWithdrawnOnBlock(block uint64) []common // Get the withdrawals list for a given block. func (wh WithdrawalsHistory) GetWithdrawals(block uint64) types.Withdrawals { - if w, ok := wh[block]; ok { + if w, ok := wh[block]; ok && w != nil { return w } return make(types.Withdrawals, 0) @@ -337,7 +424,7 @@ func (wh WithdrawalsHistory) GetWithdrawals(block uint64) types.Withdrawals { func (wh WithdrawalsHistory) GetWithdrawnAccounts(blockHeight uint64) map[common.Address]*big.Int { accounts := make(map[common.Address]*big.Int) for block := uint64(0); block <= blockHeight; block++ { - if withdrawals, ok := wh[block]; ok { + if withdrawals, ok := wh[block]; ok && withdrawals != nil { for _, withdrawal := range withdrawals { if currentBalance, ok2 := accounts[withdrawal.Address]; ok2 { currentBalance.Add(currentBalance, withdrawal.Amount) @@ -373,10 +460,10 @@ func (wh WithdrawalsHistory) Copy() WithdrawalsHistory { // on genesis or afterwards. type WithdrawalsBaseSpec struct { test.Spec - BlockTimestampIncrements int64 // Timestamp increments per block throughout the test - WithdrawalsForkHeight int64 // Withdrawals activation fork height - WithdrawalsBlockCount int // Number of blocks on and after withdrawals fork activation - WithdrawalsPerBlock int // Number of withdrawals per block + TimeIncrements uint64 // Timestamp increments per block throughout the test + WithdrawalsForkHeight uint64 // Withdrawals activation fork height + WithdrawalsBlockCount uint64 // Number of blocks on and after withdrawals fork activation + WithdrawalsPerBlock uint64 // Number of withdrawals per block WithdrawableAccountCount uint64 // Number of accounts to withdraw to (round-robin) WithdrawalsHistory WithdrawalsHistory // Internal withdrawals history that keeps track of all withdrawals WithdrawAmounts []*big.Int // Amounts of withdrawn wei on each withdrawal (round-robin) @@ -384,42 +471,47 @@ type WithdrawalsBaseSpec struct { } // Get the per-block timestamp increments configured for this test -func (ws WithdrawalsBaseSpec) GetBlockTimestampIncrements() int64 { - if ws.BlockTimestampIncrements == 0 { +func (ws WithdrawalsBaseSpec) GetBlockTimeIncrements() uint64 { + if ws.TimeIncrements == 0 { return 1 } - return ws.BlockTimestampIncrements + return ws.TimeIncrements +} + +// Timestamp delta between genesis and the withdrawals fork +func (ws WithdrawalsBaseSpec) GetWithdrawalsGenesisTimeDelta() uint64 { + return ws.WithdrawalsForkHeight * ws.GetBlockTimeIncrements() } // Calculates Shanghai fork timestamp given the amount of blocks that need to be -// produced beforehand (CLMock produces 1 block each second). -func (ws WithdrawalsBaseSpec) GetWithdrawalsTimestamp() int64 { - return globals.GenesisTimestamp + (ws.WithdrawalsForkHeight * ws.GetBlockTimestampIncrements()) +// produced beforehand. +func (ws WithdrawalsBaseSpec) GetWithdrawalsForkTime() uint64 { + return uint64(globals.GenesisTimestamp) + ws.GetWithdrawalsGenesisTimeDelta() } // Generates the fork config, including withdrawals fork timestamp. func (ws WithdrawalsBaseSpec) GetForkConfig() test.ForkConfig { return test.ForkConfig{ - ShanghaiTimestamp: big.NewInt(ws.GetWithdrawalsTimestamp()), + ShanghaiTimestamp: big.NewInt(int64(ws.GetWithdrawalsForkTime())), } } func (ws WithdrawalsBaseSpec) ConfigureCLMock(cl *clmock.CLMocker) { - cl.BlockTimestampIncrement = big.NewInt(ws.GetBlockTimestampIncrements()) + cl.BlockTimestampIncrement = big.NewInt(int64(ws.GetBlockTimeIncrements())) } // Number of blocks to be produced (not counting genesis) before withdrawals // fork. -func (ws WithdrawalsBaseSpec) GetPreWithdrawalsBlockCount() int { +func (ws WithdrawalsBaseSpec) GetPreWithdrawalsBlockCount() uint64 { if ws.WithdrawalsForkHeight == 0 { return 0 } else { - return int(ws.WithdrawalsForkHeight - 1) + return ws.WithdrawalsForkHeight - 1 } } // Number of payloads to be produced (pre and post withdrawals) during the entire test -func (ws WithdrawalsBaseSpec) GetTotalPayloadCount() int { +func (ws WithdrawalsBaseSpec) GetTotalPayloadCount() uint64 { return ws.GetPreWithdrawalsBlockCount() + ws.WithdrawalsBlockCount } @@ -442,12 +534,12 @@ func (ws WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, star } nextWithdrawals := make(types.Withdrawals, 0) - for i := 0; i < ws.WithdrawalsPerBlock; i++ { + for i := uint64(0); i < ws.WithdrawalsPerBlock; i++ { nextAccount := new(big.Int).Set(startAccount) nextAccount.Add(nextAccount, big.NewInt(int64(nextIndex%differentAccounts))) nextWithdrawal := &types.Withdrawal{ Index: nextIndex, - Validator: uint64(nextIndex), + Validator: nextIndex, Address: common.BigToAddress(nextAccount), Amount: withdrawAmounts[int(nextIndex)%len(withdrawAmounts)], } @@ -465,7 +557,7 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { t.CLMock.WaitForTTD() // Check if we have pre-Shanghai blocks - if ws.GetWithdrawalsTimestamp() > globals.GenesisTimestamp { + if ws.GetWithdrawalsForkTime() > uint64(globals.GenesisTimestamp) { // Check `latest` during all pre-shanghai blocks, none should // contain `withdrawalsRoot`, including genesis. @@ -479,7 +571,7 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { } // Produce any blocks necessary to reach withdrawals fork - t.CLMock.ProduceBlocks(ws.GetPreWithdrawalsBlockCount(), clmock.BlockProcessCallbacks{ + t.CLMock.ProduceBlocks(int(ws.GetPreWithdrawalsBlockCount()), clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { // Try to send a ForkchoiceUpdatedV2 with non-null // withdrawals before Shanghai @@ -524,7 +616,7 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { nextIndex = uint64(0) ) - t.CLMock.ProduceBlocks(ws.WithdrawalsBlockCount, clmock.BlockProcessCallbacks{ + t.CLMock.ProduceBlocks(int(ws.WithdrawalsBlockCount), clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { // Send some withdrawals t.CLMock.NextWithdrawals, nextIndex = ws.GenerateWithdrawalsForBlock(nextIndex, startAccount) @@ -579,7 +671,7 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { // Check the correct withdrawal root on past blocks r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) var expectedWithdrawalsRoot *common.Hash = nil - if block >= uint64(ws.WithdrawalsForkHeight) { + if block >= ws.WithdrawalsForkHeight { calcWithdrawalsRoot := types.DeriveSha( ws.WithdrawalsHistory.GetWithdrawals(block), trie.NewStackTrie(nil), @@ -650,32 +742,40 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { type WithdrawalsReorgSpec struct { WithdrawalsBaseSpec - ReOrgDepth int // Depth of the block re-org - ReOrgViaSync bool // Whether the client should fetch the sidechain from the secondary client - SidechainBlockTimestampIncrements int64 + 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 } func (ws WithdrawalsReorgSpec) GetSidechainSplitHeight() uint64 { - if ws.ReOrgDepth > ws.GetTotalPayloadCount() { + if ws.ReOrgBlockCount > ws.GetTotalPayloadCount() { panic("invalid payload/re-org configuration") } - return uint64(ws.GetTotalPayloadCount() - ws.ReOrgDepth + 1) + return ws.GetTotalPayloadCount() + 1 - ws.ReOrgBlockCount } -func (ws WithdrawalsReorgSpec) GetSidechainBlockTimestampIncrements() int64 { - if ws.SidechainBlockTimestampIncrements == 0 { - return ws.GetBlockTimestampIncrements() +func (ws WithdrawalsReorgSpec) GetSidechainBlockTimeIncrements() uint64 { + if ws.SidechainTimeIncrements == 0 { + return ws.GetBlockTimeIncrements() } - return ws.SidechainBlockTimestampIncrements + return ws.SidechainTimeIncrements } -func (ws WithdrawalsReorgSpec) GetSidechainWithdrawalsForkHeight() int64 { - if ws.GetSidechainBlockTimestampIncrements() != ws.GetBlockTimestampIncrements() { +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() < uint64(ws.WithdrawalsForkHeight) { + 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 := globals.GenesisTimestamp + (int64(ws.GetSidechainSplitHeight()) * ws.GetBlockTimestampIncrements()) - return ((ws.GetWithdrawalsTimestamp() - sidechainSplitBlockTimestamp) / ws.SidechainBlockTimestampIncrements) + int64(ws.GetSidechainSplitHeight()) + sidechainSplitBlockTimestamp := ((ws.GetSidechainSplitHeight() - 1) * ws.GetBlockTimeIncrements()) + remainingTime := (ws.GetWithdrawalsGenesisTimeDelta() - sidechainSplitBlockTimestamp) + if remainingTime == 0 { + return ws.GetSidechainSplitHeight() + } + return ((remainingTime - 1) / ws.SidechainTimeIncrements) + ws.GetSidechainSplitHeight() } } @@ -694,7 +794,7 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) } secondaryEngineTest := test.NewTestEngineClient(t, secondaryEngine) - t.CLMock.AddEngineClient(secondaryEngine) + // t.CLMock.AddEngineClient(secondaryEngine) var ( canonicalStartAccount = big.NewInt(0x1000) @@ -709,20 +809,28 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { // Sidechain withdraws on the max account value range 0xffffffffffffffffffffffffffffffffffffffff sidechainStartAccount.Sub(sidechainStartAccount, big.NewInt(int64(ws.GetWithdrawableAccountCount())+1)) - t.CLMock.ProduceBlocks(ws.GetPreWithdrawalsBlockCount()+ws.WithdrawalsBlockCount, clmock.BlockProcessCallbacks{ + t.CLMock.ProduceBlocks(int(ws.GetPreWithdrawalsBlockCount()+ws.WithdrawalsBlockCount), clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { - if t.CLMock.CurrentPayloadNumber >= uint64(ws.WithdrawalsForkHeight) { - // Send some withdrawals + t.CLMock.NextWithdrawals = nil + + if t.CLMock.CurrentPayloadNumber >= ws.WithdrawalsForkHeight { + // Prepare some withdrawals t.CLMock.NextWithdrawals, canonicalNextIndex = ws.GenerateWithdrawalsForBlock(canonicalNextIndex, canonicalStartAccount) ws.WithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals - if t.CLMock.CurrentPayloadNumber >= ws.GetSidechainSplitHeight() { - // We also need to generate withdrawals for the sidechain + } + + 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 sidechainWithdrawalsHistory[t.CLMock.CurrentPayloadNumber], sidechainNextIndex = ws.GenerateWithdrawalsForBlock(sidechainNextIndex, sidechainStartAccount) - } else { - sidechainWithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals - sidechainNextIndex = canonicalNextIndex - } + } // else nothing to do + } else { + // We have not split + sidechainWithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals + sidechainNextIndex = canonicalNextIndex } + }, OnRequestNextPayload: func() { if t.CLMock.CurrentPayloadNumber >= ws.GetSidechainSplitHeight() { @@ -739,15 +847,19 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { } } - t.Logf("INFO (%s): Requesting sidechain payload %d", t.TestName, t.CLMock.CurrentPayloadNumber) - var version int pAttributes := beacon.PayloadAttributes{ - Timestamp: t.CLMock.LatestPayloadAttributes.Timestamp, Random: t.CLMock.LatestPayloadAttributes.Random, SuggestedFeeRecipient: t.CLMock.LatestPayloadAttributes.SuggestedFeeRecipient, } - if t.CLMock.CurrentPayloadNumber >= uint64(ws.WithdrawalsForkHeight) { + 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 = sidechainWithdrawalsHistory[t.CLMock.CurrentPayloadNumber] @@ -755,6 +867,9 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { // No withdrawals version = 1 } + + t.Logf("INFO (%s): Requesting sidechain payload %d: %v", t.TestName, t.CLMock.CurrentPayloadNumber, pAttributes) + r := secondaryEngineTest.TestEngineForkchoiceUpdated(&fcU, &pAttributes, version) r.ExpectNoError() r.ExpectPayloadStatus(test.Valid) @@ -769,13 +884,13 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { version int payload *beacon.ExecutableData ) - if t.CLMock.CurrentPayloadNumber >= uint64(ws.WithdrawalsForkHeight) { + if t.CLMock.CurrentPayloadNumber >= ws.GetSidechainWithdrawalsForkHeight() { version = 2 } else { version = 1 } if t.CLMock.LatestPayloadBuilt.Number >= ws.GetSidechainSplitHeight() { - // This payload is built on top of a sidechain payload, get the payload for that too + // This payload is built by the secondary client, hence need to manually fetch it here r := secondaryEngineTest.TestEngineGetPayload(sidechainPayloadId, version) r.ExpectNoError() payload = &r.Payload @@ -797,11 +912,45 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { }, }) - latestPayloadHeight := t.CLMock.LatestExecutedPayload.Number + 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++ { + sidechainWithdrawalsHistory[sidechainHeight+1], sidechainNextIndex = ws.GenerateWithdrawalsForBlock(sidechainNextIndex, sidechainStartAccount) + pAttributes := beacon.PayloadAttributes{ + Timestamp: sidechain[sidechainHeight].Timestamp + ws.GetSidechainBlockTimeIncrements(), + Random: t.CLMock.LatestPayloadAttributes.Random, + SuggestedFeeRecipient: t.CLMock.LatestPayloadAttributes.SuggestedFeeRecipient, + Withdrawals: sidechainWithdrawalsHistory[sidechainHeight+1], + } + r := secondaryEngineTest.TestEngineForkchoiceUpdatedV2(&beacon.ForkchoiceStateV1{ + HeadBlockHash: sidechain[sidechainHeight].BlockHash, + }, &pAttributes) + r.ExpectPayloadStatus(test.Valid) + time.Sleep(time.Second) + p := secondaryEngineTest.TestEngineGetPayloadV2(r.Response.PayloadID) + p.ExpectNoError() + s := secondaryEngineTest.TestEngineNewPayloadV2(&p.Payload) + s.ExpectStatus(test.Valid) + q := secondaryEngineTest.TestEngineForkchoiceUpdatedV2( + &beacon.ForkchoiceStateV1{ + HeadBlockHash: p.Payload.BlockHash, + }, + nil, + ) + q.ExpectPayloadStatus(test.Valid) + sidechainHeight++ + sidechain[sidechainHeight] = &p.Payload + } + } // Check the withdrawals on the latest ws.WithdrawalsHistory.VerifyWithdrawals( - latestPayloadHeight, + sidechainHeight, nil, t.TestEngine, ) @@ -810,11 +959,11 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { // Send latest sidechain payload as NewPayload + FCU and wait for sync loop: for { - r := t.TestEngine.TestEngineNewPayloadV2(sidechain[latestPayloadHeight]) + r := t.TestEngine.TestEngineNewPayloadV2(sidechain[sidechainHeight]) r.ExpectNoError() p := t.TestEngine.TestEngineForkchoiceUpdatedV2( &beacon.ForkchoiceStateV1{ - HeadBlockHash: sidechain[latestPayloadHeight].BlockHash, + HeadBlockHash: sidechain[sidechainHeight].BlockHash, }, nil, ) @@ -824,7 +973,7 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { t.Fatalf("FAIL (%s): Timeout waiting for sync", t.TestName) case <-time.After(time.Second): b := t.TestEngine.TestBlockByNumber(nil) - if b.Block.Hash() == sidechain[latestPayloadHeight].BlockHash { + if b.Block.Hash() == sidechain[sidechainHeight].BlockHash { // sync successful break loop } @@ -832,13 +981,13 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { } } else { // Send all payloads one by one to the primary client - for payloadNumber := latestPayloadHeight - uint64(ws.ReOrgDepth) + 1; payloadNumber <= latestPayloadHeight; payloadNumber++ { + for payloadNumber := ws.GetSidechainSplitHeight(); payloadNumber <= sidechainHeight; payloadNumber++ { payload, ok := sidechain[payloadNumber] if !ok { t.Fatalf("FAIL (%s): Invalid payload %d requested.", t.TestName, payloadNumber) } var version int - if payloadNumber >= uint64(ws.WithdrawalsForkHeight) { + if payloadNumber >= ws.GetSidechainWithdrawalsForkHeight() { version = 2 } else { version = 1 @@ -859,7 +1008,7 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { // Verify withdrawals changed sidechainWithdrawalsHistory.VerifyWithdrawals( - latestPayloadHeight, + sidechainHeight, nil, t.TestEngine, ) @@ -869,9 +1018,14 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { // and the fork. // We check on `latest`. ws.WithdrawalsHistory.VerifyWithdrawals( - uint64(ws.WithdrawalsForkHeight)-1, + ws.WithdrawalsForkHeight-1, nil, t.TestEngine, ) + // Re-Org back to the canonical chain + r := t.TestEngine.TestEngineForkchoiceUpdatedV2(&beacon.ForkchoiceStateV1{ + HeadBlockHash: t.CLMock.LatestPayloadBuilt.BlockHash, + }, nil) + r.ExpectPayloadStatus(test.Valid) } From 2483d799fa3bd4b7f750aaee8ff91c9b819cff92 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 25 Nov 2022 14:18:34 -0600 Subject: [PATCH 20/49] simulators/ethereum/engine: Add LVH check --- simulators/ethereum/engine/suites/withdrawals/tests.go | 1 + 1 file changed, 1 insertion(+) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index e8c6c6907b..f2a8df0714 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -600,6 +600,7 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { } r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) r.ExpectStatus(test.Invalid) + r.ExpectLatestValidHash(&t.CLMock.LatestExecutedPayload.BlockHash) }, OnNewPayloadBroadcast: func() { // We sent a pre-shanghai FCU. From 96118422750d2893d98ecaadc76f49aa7a2da3c7 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 28 Nov 2022 15:15:59 -0600 Subject: [PATCH 21/49] simulators/ethereum/engine: Dynamic genesis --- simulators/ethereum/engine/client/engine.go | 3 +- .../engine/client/hive_rpc/hive_rpc.go | 14 +-- .../ethereum/engine/client/node/node.go | 22 ++--- simulators/ethereum/engine/helper/helper.go | 17 +++- simulators/ethereum/engine/main.go | 95 +++++++++++++------ .../ethereum/engine/suites/engine/tests.go | 14 +-- .../ethereum/engine/suites/sync/tests.go | 9 +- .../engine/suites/transition/tests.go | 2 +- .../engine/suites/withdrawals/tests.go | 67 ++++++++++++- simulators/ethereum/engine/test/env.go | 5 +- simulators/ethereum/engine/test/spec.go | 13 ++- 11 files changed, 190 insertions(+), 71 deletions(-) diff --git a/simulators/ethereum/engine/client/engine.go b/simulators/ethereum/engine/client/engine.go index d59ca53a8a..4e937a8fea 100644 --- a/simulators/ethereum/engine/client/engine.go +++ b/simulators/ethereum/engine/client/engine.go @@ -4,6 +4,7 @@ import ( "context" "math/big" + "github.com/ethereum/go-ethereum/core" api "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/hive/hivesim" @@ -61,7 +62,7 @@ type EngineClient interface { } type EngineStarter interface { - StartClient(T *hivesim.T, testContext context.Context, ClientParams hivesim.Params, ClientFiles hivesim.Params, bootClients ...EngineClient) (EngineClient, error) + StartClient(T *hivesim.T, testContext context.Context, genesis *core.Genesis, ClientParams hivesim.Params, ClientFiles hivesim.Params, bootClients ...EngineClient) (EngineClient, error) } var ( diff --git a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go index a963dfa3f0..bf60e96aec 100644 --- a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go +++ b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" api "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" @@ -34,7 +35,7 @@ type HiveRPCEngineStarter struct { JWTSecret []byte } -func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Context, ClientParams hivesim.Params, ClientFiles hivesim.Params, bootClients ...client.EngineClient) (client.EngineClient, error) { +func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Context, genesis *core.Genesis, ClientParams hivesim.Params, ClientFiles hivesim.Params, bootClients ...client.EngineClient) (client.EngineClient, error) { var ( clientType = s.ClientType enginePort = s.EnginePort @@ -64,9 +65,6 @@ func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Cont if s.ChainFile != "" { ClientFiles = ClientFiles.Set("/chain.rlp", "./chains/"+s.ChainFile) } - if _, ok := ClientFiles["/genesis.json"]; !ok { - return nil, fmt.Errorf("cannot start without genesis file") - } if ttd == nil { if ttdStr, ok := ClientParams["HIVE_TERMINAL_TOTAL_DIFFICULTY"]; ok { // Retrieve TTD from parameters @@ -77,7 +75,7 @@ func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Cont } } else { // Real TTD must be calculated adding the genesis difficulty - ttdInt := helper.CalculateRealTTD(ClientFiles["/genesis.json"], ttd.Int64()) + ttdInt := helper.CalculateRealTTD(genesis, ttd.Int64()) ClientParams = ClientParams.Set("HIVE_TERMINAL_TOTAL_DIFFICULTY", fmt.Sprintf("%d", ttdInt)) ttd = big.NewInt(ttdInt) } @@ -97,7 +95,11 @@ func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Cont } // Start the client and create the engine client object - c := T.StartClient(clientType, ClientParams, hivesim.WithStaticFiles(ClientFiles)) + genesisStart, err := helper.GenesisStartOption(genesis) + if err != nil { + return nil, err + } + c := T.StartClient(clientType, genesisStart, ClientParams, hivesim.WithStaticFiles(ClientFiles)) if err := CheckEthEngineLive(c); err != nil { return nil, fmt.Errorf("Engine/Eth ports were never open for client: %v", err) } diff --git a/simulators/ethereum/engine/client/node/node.go b/simulators/ethereum/engine/client/node/node.go index 9e2a83b624..baeb966dad 100644 --- a/simulators/ethereum/engine/client/node/node.go +++ b/simulators/ethereum/engine/client/node/node.go @@ -91,20 +91,15 @@ var ( DefaultTerminalBlockSiblingDepth = big.NewInt(1) ) -func (s GethNodeEngineStarter) StartClient(T *hivesim.T, testContext context.Context, ClientParams hivesim.Params, ClientFiles hivesim.Params, bootClients ...client.EngineClient) (client.EngineClient, error) { - return s.StartGethNode(T, testContext, ClientParams, ClientFiles, bootClients...) +func (s GethNodeEngineStarter) StartClient(T *hivesim.T, testContext context.Context, genesis *core.Genesis, ClientParams hivesim.Params, ClientFiles hivesim.Params, bootClients ...client.EngineClient) (client.EngineClient, error) { + return s.StartGethNode(T, testContext, genesis, ClientParams, ClientFiles, bootClients...) } -func (s GethNodeEngineStarter) StartGethNode(T *hivesim.T, testContext context.Context, ClientParams hivesim.Params, ClientFiles hivesim.Params, bootClients ...client.EngineClient) (*GethNode, error) { +func (s GethNodeEngineStarter) StartGethNode(T *hivesim.T, testContext context.Context, genesis *core.Genesis, ClientParams hivesim.Params, ClientFiles hivesim.Params, bootClients ...client.EngineClient) (*GethNode, error) { var ( ttd = s.TerminalTotalDifficulty err error ) - genesisPath, ok := ClientFiles["/genesis.json"] - if !ok { - return nil, fmt.Errorf("Cannot start without genesis file") - } - genesis := helper.LoadGenesis(genesisPath) if ttd == nil { if ttdStr, ok := ClientParams["HIVE_TERMINAL_TOTAL_DIFFICULTY"]; ok { @@ -115,10 +110,15 @@ func (s GethNodeEngineStarter) StartGethNode(T *hivesim.T, testContext context.C } } } else { - ttd = big.NewInt(helper.CalculateRealTTD(genesisPath, ttd.Int64())) + ttd = big.NewInt(helper.CalculateRealTTD(genesis, ttd.Int64())) ClientParams = ClientParams.Set("HIVE_TERMINAL_TOTAL_DIFFICULTY", fmt.Sprintf("%d", ttd)) } - genesis.Config.TerminalTotalDifficulty = ttd + + // Not sure if this hack works + genesisCopy := *genesis + configCopy := *genesisCopy.Config + configCopy.TerminalTotalDifficulty = ttd + genesisCopy.Config = &configCopy var enodes []string if bootClients != nil && len(bootClients) > 0 { @@ -151,7 +151,7 @@ func (s GethNodeEngineStarter) StartGethNode(T *hivesim.T, testContext context.C s.Config.TerminalBlockSiblingDepth = DefaultTerminalBlockSiblingDepth } - g, err := newNode(s.Config, enodes, &genesis) + g, err := newNode(s.Config, enodes, &genesisCopy) if err != nil { return nil, err } diff --git a/simulators/ethereum/engine/helper/helper.go b/simulators/ethereum/engine/helper/helper.go index e760a2c969..164617df6d 100644 --- a/simulators/ethereum/engine/helper/helper.go +++ b/simulators/ethereum/engine/helper/helper.go @@ -172,6 +172,12 @@ func nethermindDebugPrevRandaoTransaction(ctx context.Context, c *rpc.Client, tx return nil } +func bytesSource(data []byte) func() (io.ReadCloser, error) { + return func() (io.ReadCloser, error) { + return ioutil.NopCloser(bytes.NewReader(data)), nil + } +} + func LoadChain(path string) types.Blocks { fh, err := os.Open(path) if err != nil { @@ -211,6 +217,14 @@ func LoadGenesisBlock(path string) *types.Block { return genesis.ToBlock() } +func GenesisStartOption(genesis *core.Genesis) (hivesim.StartOption, error) { + out, err := json.Marshal(genesis) + if err != nil { + return nil, fmt.Errorf("failed to serialize genesis state: %v", err) + } + return hivesim.WithDynamicFile("/genesis.json", bytesSource(out)), nil +} + func CalculateTotalDifficulty(genesis core.Genesis, chain types.Blocks, lastBlock uint64) *big.Int { result := new(big.Int).Set(genesis.Difficulty) for _, b := range chain { @@ -223,8 +237,7 @@ func CalculateTotalDifficulty(genesis core.Genesis, chain types.Blocks, lastBloc } // TTD is the value specified in the test.Spec + Genesis.Difficulty -func CalculateRealTTD(genesisPath string, ttdValue int64) int64 { - g := LoadGenesis(genesisPath) +func CalculateRealTTD(g *core.Genesis, ttdValue int64) int64 { return g.Difficulty.Int64() + ttdValue } diff --git a/simulators/ethereum/engine/main.go b/simulators/ethereum/engine/main.go index 67f3672a6e..6fbe0cda52 100644 --- a/simulators/ethereum/engine/main.go +++ b/simulators/ethereum/engine/main.go @@ -10,6 +10,9 @@ import ( "github.com/ethereum/hive/simulators/ethereum/engine/helper" "github.com/ethereum/hive/simulators/ethereum/engine/test" + suite_auth "github.com/ethereum/hive/simulators/ethereum/engine/suites/auth" + suite_engine "github.com/ethereum/hive/simulators/ethereum/engine/suites/engine" + suite_transition "github.com/ethereum/hive/simulators/ethereum/engine/suites/transition" suite_withdrawals "github.com/ethereum/hive/simulators/ethereum/engine/suites/withdrawals" ) @@ -46,11 +49,11 @@ func main() { simulator := hivesim.New() - //addTestsToSuite(&engine, suite_engine.Tests, "full") - //addTestsToSuite(&transition, suite_transition.Tests, "full") - //addTestsToSuite(&auth, suite_auth.Tests, "full") + addTestsToSuite(simulator, &engine, specToInterface(suite_engine.Tests), "full") + addTestsToSuite(simulator, &transition, specToInterface(suite_transition.Tests), "full") + addTestsToSuite(simulator, &auth, specToInterface(suite_auth.Tests), "full") //suite_sync.AddSyncTestsToSuite(simulator, &sync, suite_sync.Tests) - addTestsToSuite(&withdrawals, suite_withdrawals.Tests, "full") + addTestsToSuite(simulator, &withdrawals, suite_withdrawals.Tests, "full") // Mark suites for execution hivesim.MustRunSuite(simulator, engine) @@ -60,19 +63,28 @@ func main() { hivesim.MustRunSuite(simulator, withdrawals) } +func specToInterface(src []test.Spec) []test.SpecInterface { + res := make([]test.SpecInterface, len(src)) + for i := 0; i < len(src); i++ { + res[i] = src[i] + } + return res +} + // Add test cases to a given test suite -func addTestsToSuite(suite *hivesim.Suite, tests []test.SpecInterface, nodeType string) { +func addTestsToSuite(sim *hivesim.Simulation, suite *hivesim.Suite, tests []test.SpecInterface, nodeType string) { for _, currentTest := range tests { currentTest := currentTest - genesisPath := "./init/genesis.json" - // If the test.Spec specified a custom genesis file, use that instead. - if currentTest.GetGenesisFile() != "" { - genesisPath = "./init/" + currentTest.GetGenesisFile() + + // Load the genesis file specified and dynamically bundle it. + genesis := currentTest.GetGenesis() + genesisStartOption, err := helper.GenesisStartOption(genesis) + if err != nil { + panic("unable to inject genesis") } - // Load genesis for it to be modified before starting the client - testFiles := hivesim.Params{"/genesis.json": genesisPath} + // Calculate and set the TTD for this test - ttd := helper.CalculateRealTTD(genesisPath, currentTest.GetTTD()) + ttd := helper.CalculateRealTTD(genesis, currentTest.GetTTD()) // Configure Forks newParams := globals.DefaultClientEnv.Set("HIVE_TERMINAL_TOTAL_DIFFICULTY", fmt.Sprintf("%d", ttd)) if currentTest.GetForkConfig().ShanghaiTimestamp != nil { @@ -83,6 +95,7 @@ func addTestsToSuite(suite *hivesim.Suite, tests []test.SpecInterface, nodeType newParams = newParams.Set("HIVE_NODETYPE", nodeType) } + testFiles := hivesim.Params{} if currentTest.GetChainFile() != "" { // We are using a Proof of Work chain file, remove all clique-related settings // TODO: Nethermind still requires HIVE_MINER for the Engine API @@ -95,24 +108,44 @@ func addTestsToSuite(suite *hivesim.Suite, tests []test.SpecInterface, nodeType if currentTest.IsMiningDisabled() { delete(newParams, "HIVE_MINER") } - suite.Add(hivesim.ClientTestSpec{ - Name: currentTest.GetName(), - Description: currentTest.GetAbout(), - Parameters: newParams, - Files: testFiles, - Run: func(t *hivesim.T, c *hivesim.Client) { - t.Logf("Start test (%s): %s", c.Type, currentTest.GetName()) - defer func() { - t.Logf("End test (%s): %s", c.Type, currentTest.GetName()) - }() - timeout := globals.DefaultTestCaseTimeout - // If a test.Spec specifies a timeout, use that instead - if currentTest.GetTimeout() != 0 { - timeout = time.Second * time.Duration(currentTest.GetTimeout()) - } - // Run the test case - test.Run(currentTest, big.NewInt(ttd), timeout, t, c, newParams, testFiles) - }, - }) + + if clientTypes, err := sim.ClientTypes(); err == nil { + for _, clientType := range clientTypes { + suite.Add(hivesim.TestSpec{ + Name: fmt.Sprintf("%s (%s)", currentTest.GetName(), clientType.Name), + Description: currentTest.GetAbout(), + Run: func(t *hivesim.T) { + // Start the client with given options + c := t.StartClient( + clientType.Name, + newParams, + genesisStartOption, + hivesim.WithStaticFiles(testFiles), + ) + t.Logf("Start test (%s): %s", c.Type, currentTest.GetName()) + defer func() { + t.Logf("End test (%s): %s", c.Type, currentTest.GetName()) + }() + timeout := globals.DefaultTestCaseTimeout + // If a test.Spec specifies a timeout, use that instead + if currentTest.GetTimeout() != 0 { + timeout = time.Second * time.Duration(currentTest.GetTimeout()) + } + // Run the test case + test.Run( + currentTest, + big.NewInt(ttd), + timeout, + t, + c, + genesis, + newParams, + testFiles, + ) + }, + }) + } + } + } } diff --git a/simulators/ethereum/engine/suites/engine/tests.go b/simulators/ethereum/engine/suites/engine/tests.go index 81ead251ae..c3e6f592f3 100644 --- a/simulators/ethereum/engine/suites/engine/tests.go +++ b/simulators/ethereum/engine/suites/engine/tests.go @@ -1697,7 +1697,7 @@ func invalidPayloadTestCaseGen(payloadField helper.InvalidPayloadBlockField, syn if syncing { // To allow sending the primary engine client into SYNCING state, we need a secondary client to guide the payload creation - secondaryClient, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles) + secondaryClient, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles) if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) } @@ -2067,9 +2067,9 @@ func (spec InvalidMissingAncestorReOrgSpec) GenerateSync() func(*test.Env) { } if spec.ReOrgFromCanonical { // If we are doing a re-org from canonical, we can add both nodes as peers from the start - secondaryClient, err = starter.StartGethNode(t.T, t.TestContext, t.ClientParams, t.ClientFiles, t.Engine) + secondaryClient, err = starter.StartGethNode(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles, t.Engine) } else { - secondaryClient, err = starter.StartGethNode(t.T, t.TestContext, t.ClientParams, t.ClientFiles) + secondaryClient, err = starter.StartGethNode(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles) } if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) @@ -3168,7 +3168,7 @@ func inOrderPayloads(t *test.Env) { r.ExpectBalanceEqual(expectedBalance) // Start a second client to send newPayload consecutively without fcU - secondaryClient, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles) + secondaryClient, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles) if err != nil { t.Fatalf("FAIL (%s): Unable to start secondary client: %v", t.TestName, err) } @@ -3220,7 +3220,7 @@ func validPayloadFcUSyncingClient(t *test.Env) { { // To allow sending the primary engine client into SYNCING state, we need a secondary client to guide the payload creation var err error - secondaryClient, err = hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles) + secondaryClient, err = hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles) if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) @@ -3304,7 +3304,7 @@ func missingFcu(t *test.Env) { var secondaryEngineTest *test.TestEngineClient { - secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles) + secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles) if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) @@ -3345,7 +3345,7 @@ func missingFcu(t *test.Env) { // P <- INV_P, newPayload(INV_P), fcU(head: P, payloadAttributes: attrs) + getPayload(…) func payloadBuildAfterNewInvalidPayload(t *test.Env) { // Add a second client to build the invalid payload - secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles) + secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles) if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) diff --git a/simulators/ethereum/engine/suites/sync/tests.go b/simulators/ethereum/engine/suites/sync/tests.go index 29f0537994..95414240ef 100644 --- a/simulators/ethereum/engine/suites/sync/tests.go +++ b/simulators/ethereum/engine/suites/sync/tests.go @@ -52,7 +52,8 @@ func AddSyncTestsToSuite(sim *hivesim.Simulation, suite *hivesim.Suite, tests [] } testFiles := hivesim.Params{"/genesis.json": genesisPath} // Calculate and set the TTD for this test - ttd := helper.CalculateRealTTD(genesisPath, currentTest.TTD) + genesis := helper.LoadGenesis(genesisPath) + ttd := helper.CalculateRealTTD(&genesis, currentTest.TTD) newParams := globals.DefaultClientEnv.Set("HIVE_TERMINAL_TOTAL_DIFFICULTY", fmt.Sprintf("%d", ttd)) if currentTest.ChainFile != "" { // We are using a Proof of Work chain file, remove all clique-related settings @@ -98,7 +99,7 @@ func AddSyncTestsToSuite(sim *hivesim.Simulation, suite *hivesim.Suite, tests [] } // Run the test case - test.Run(currentTest, big.NewInt(ttd), timeout, t, c, syncClientParams, testFiles.Copy()) + test.Run(currentTest, big.NewInt(ttd), timeout, t, c, &genesis, syncClientParams, testFiles.Copy()) }, }) } @@ -126,7 +127,7 @@ func postMergeSync(t *test.Env) { // Reset block production delay t.CLMock.PayloadProductionClientDelay = time.Second - secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams.Set("HIVE_MINER", ""), t.ClientFiles, t.Engine) + secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams.Set("HIVE_MINER", ""), t.ClientFiles, t.Engine) if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) } @@ -182,7 +183,7 @@ func incrementalPostMergeSync(t *test.Env) { // Reset block production delay t.CLMock.PayloadProductionClientDelay = time.Second - secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams.Set("HIVE_MINER", ""), t.ClientFiles, t.Engine) + secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams.Set("HIVE_MINER", ""), t.ClientFiles, t.Engine) if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) } diff --git a/simulators/ethereum/engine/suites/transition/tests.go b/simulators/ethereum/engine/suites/transition/tests.go index dfe231c779..daa1854305 100644 --- a/simulators/ethereum/engine/suites/transition/tests.go +++ b/simulators/ethereum/engine/suites/transition/tests.go @@ -946,7 +946,7 @@ func GenerateMergeTestSpec(mergeTestSpec MergeTestSpec) test.Spec { for i, secondaryClientSpec := range mergeTestSpec.SecondaryClientSpecs { // Start the secondary client with the alternative chain - secondaryClient, err := secondaryClientSpec.ClientStarter.StartClient(t.T, t.CLMock.TestContext, t.ClientParams, t.ClientFiles, t.Engine) + secondaryClient, err := secondaryClientSpec.ClientStarter.StartClient(t.T, t.CLMock.TestContext, t.Genesis, t.ClientParams, t.ClientFiles, t.Engine) t.Logf("INFO (%s): Started secondary client: %v", t.TestName, secondaryClient.ID()) if err != nil { t.Fatalf("FAIL (%s): Unable to start secondary client: %v", t.TestName, err) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index f2a8df0714..d30adcbace 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -6,6 +6,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/trie" @@ -122,7 +123,22 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 1, WithdrawalsPerBlock: 64, WithdrawableAccountCount: 2, - WithdrawAmounts: []*big.Int{common.Big0, common.Big1}, + WithdrawAmounts: []*big.Int{ + common.Big0, + common.Big1, + }, + }, + + WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Empty Withdrawals", + About: ` + Produce withdrawals block with zero withdrawals. + `, + }, + WithdrawalsForkHeight: 1, + WithdrawalsBlockCount: 1, + WithdrawalsPerBlock: 0, }, // Sync Tests @@ -202,6 +218,8 @@ var Tests = []test.SpecInterface{ }, SyncSteps: 1, }, + + //Re-Org tests WithdrawalsReorgSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ @@ -443,6 +461,11 @@ func (wh WithdrawalsHistory) VerifyWithdrawals(block uint64, rpcBlock *big.Int, for account, expectedBalance := range accounts { r := testEngine.TestBalanceAt(account, rpcBlock) r.ExpectBalanceEqual(expectedBalance) + // All withdrawals account have a bytecode that unconditionally set the + // zero storage key to one on EVM execution. + // Withdrawals must not trigger EVM so we expect zero. + s := testEngine.TestStorageAt(account, common.BigToHash(common.Big0), rpcBlock) + s.ExpectBigIntStorageEqual(common.Big0) } } @@ -496,6 +519,38 @@ func (ws WithdrawalsBaseSpec) GetForkConfig() test.ForkConfig { } } +// Adds bytecode that unconditionally sets an storage key to specified account range +func AddUnconditionalBytecode(g *core.Genesis, start *big.Int, end *big.Int) { + for ; start.Cmp(end) <= 0; start.Add(start, common.Big1) { + accountAddress := common.BigToAddress(start) + // Bytecode to unconditionally set a storage key + g.Alloc[accountAddress] = core.GenesisAccount{ + Code: []byte{ + 0x60, + 0x01, + 0x60, + 0x00, + 0x55, + 0x00, + }, // sstore(0, 1) + Nonce: 0, + Balance: common.Big0, + } + } +} + +// Append the accounts we are going to withdraw to, which should also include +// bytecode for testing purposes. +func (ws WithdrawalsBaseSpec) GetGenesis() *core.Genesis { + genesis := ws.Spec.GetGenesis() + startAccount := big.NewInt(0x1000) + endAccount := big.NewInt(0x1000 + int64(ws.GetWithdrawableAccountCount()) - 1) + AddUnconditionalBytecode(genesis, startAccount, endAccount) + return genesis +} + +// Changes the CL Mocker default time increments of 1 to the value specified +// in the test spec. func (ws WithdrawalsBaseSpec) ConfigureCLMock(cl *clmock.CLMocker) { cl.BlockTimestampIncrement = big.NewInt(int64(ws.GetBlockTimeIncrements())) } @@ -679,6 +734,7 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { ) expectedWithdrawalsRoot = &calcWithdrawalsRoot } + t.Logf("INFO (%s): Verifying withdrawals root on block %d (%s) to be %s", t.TestName, block, t.CLMock.ExecutedPayloadHistory[block].BlockHash, expectedWithdrawalsRoot) r.ExpectWithdrawalsRoot(expectedWithdrawalsRoot) } @@ -701,7 +757,7 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { ws.WithdrawalsBaseSpec.Execute(t) // Spawn a secondary client which will need to sync to the primary client - secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles, t.Engine) + secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles, t.Engine) if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) } @@ -723,11 +779,14 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { ) r := secondaryEngineTest.TestEngineForkchoiceUpdatedV2( &t.CLMock.LatestForkchoice, - &beacon.PayloadAttributes{}, + nil, ) if r.Response.PayloadStatus.Status == test.Valid { break loop } + if r.Response.PayloadStatus.Status == test.Invalid { + t.Fatalf("FAIL (%s): Syncing client rejected valid chain: %s", t.TestName, r.Response) + } } } @@ -790,7 +849,7 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { t.CLMock.WaitForTTD() // Spawn a secondary client which will produce the sidechain - secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.ClientParams, t.ClientFiles, t.Engine) + secondaryEngine, err := hive_rpc.HiveRPCEngineStarter{}.StartClient(t.T, t.TestContext, t.Genesis, t.ClientParams, t.ClientFiles, t.Engine) if err != nil { t.Fatalf("FAIL (%s): Unable to spawn a secondary client: %v", t.TestName, err) } diff --git a/simulators/ethereum/engine/test/env.go b/simulators/ethereum/engine/test/env.go index b28c7a6b23..4221306b2f 100644 --- a/simulators/ethereum/engine/test/env.go +++ b/simulators/ethereum/engine/test/env.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/hive/simulators/ethereum/engine/helper" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/hive/hivesim" ) @@ -38,6 +39,7 @@ type Env struct { CLMock *clmock.CLMocker // Client parameters used to launch the default client + Genesis *core.Genesis ClientParams hivesim.Params ClientFiles hivesim.Params @@ -45,7 +47,7 @@ type Env struct { TestTransactionType helper.TestTransactionType } -func Run(testSpec SpecInterface, ttd *big.Int, timeout time.Duration, t *hivesim.T, c *hivesim.Client, cParams hivesim.Params, cFiles hivesim.Params) { +func Run(testSpec SpecInterface, ttd *big.Int, timeout time.Duration, t *hivesim.T, c *hivesim.Client, genesis *core.Genesis, cParams hivesim.Params, cFiles hivesim.Params) { // Setup the CL Mocker for this test consensusConfig := testSpec.GetConsensusConfig() clMocker := clmock.NewCLMocker( @@ -82,6 +84,7 @@ func Run(testSpec SpecInterface, ttd *big.Int, timeout time.Duration, t *hivesim Eth: ec, HiveEngine: ec, CLMock: clMocker, + Genesis: genesis, ClientParams: cParams, ClientFiles: cFiles, TestTransactionType: testSpec.GetTestTransactionType(), diff --git a/simulators/ethereum/engine/test/spec.go b/simulators/ethereum/engine/test/spec.go index 4c93ed4900..0d8c906133 100644 --- a/simulators/ethereum/engine/test/spec.go +++ b/simulators/ethereum/engine/test/spec.go @@ -1,8 +1,10 @@ package test import ( + "fmt" "math/big" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/hive/simulators/ethereum/engine/clmock" "github.com/ethereum/hive/simulators/ethereum/engine/helper" ) @@ -25,7 +27,7 @@ type SpecInterface interface { GetConsensusConfig() ConsensusConfig GetChainFile() string GetForkConfig() ForkConfig - GetGenesisFile() string + GetGenesis() *core.Genesis GetName() string GetTestTransactionType() helper.TestTransactionType GetTimeout() int @@ -105,8 +107,13 @@ func (s Spec) GetForkConfig() ForkConfig { return s.ForkConfig } -func (s Spec) GetGenesisFile() string { - return s.GenesisFile +func (s Spec) GetGenesis() *core.Genesis { + genesisPath := "./init/genesis.json" + if s.GenesisFile != "" { + genesisPath = fmt.Sprintf("./init/%s", s.GenesisFile) + } + genesis := helper.LoadGenesis(genesisPath) + return &genesis } func (s Spec) GetName() string { From 152f4efc446e715e6338a963dff08a4817a3b541 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 28 Nov 2022 15:52:03 -0600 Subject: [PATCH 22/49] simulators/ethereum/engine: Fix HTTP Transport usage --- .../ethereum/engine/client/hive_rpc/hive_rpc.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go index bf60e96aec..d93104ecc0 100644 --- a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go +++ b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go @@ -167,22 +167,24 @@ type HiveRPCEngineClient struct { // NewClient creates a engine client that uses the given RPC client. func NewHiveRPCEngineClient(h *hivesim.Client, enginePort int, ethPort int, jwtSecretBytes []byte, ttd *big.Int, transport http.RoundTripper) *HiveRPCEngineClient { // Prepare HTTP Client - rpcHttpClient, err := rpc.DialHTTPWithClient(fmt.Sprintf("http://%s:%d/", h.IP, enginePort), &http.Client{Transport: transport}) + httpClient := rpc.WithHTTPClient(&http.Client{Transport: transport}) + + engineRpcClient, err := rpc.DialOptions(context.Background(), fmt.Sprintf("http://%s:%d/", h.IP, enginePort), httpClient) if err != nil { panic(err) } // Prepare ETH Client - rpcClient, err := rpc.DialHTTPWithClient(fmt.Sprintf("http://%s:%d/", h.IP, ethPort), &http.Client{Transport: transport}) + ethRpcClient, err := rpc.DialOptions(context.Background(), fmt.Sprintf("http://%s:%d/", h.IP, ethPort), httpClient) if err != nil { panic(err) } - eth := ethclient.NewClient(rpcClient) + eth := ethclient.NewClient(ethRpcClient) return &HiveRPCEngineClient{ h: h, - c: rpcHttpClient, + c: engineRpcClient, Client: eth, - cEth: rpcClient, + cEth: ethRpcClient, ttd: ttd, JWTSecretBytes: jwtSecretBytes, accTxInfoMap: make(map[common.Address]*AccountTransactionInfo), From 16ecb1810194b47480272c4ddab6c147dbab799f Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 28 Nov 2022 16:12:52 -0600 Subject: [PATCH 23/49] simulators/ethereum/engine: Roundtrip change --- .../ethereum/engine/client/hive_rpc/hive_rpc.go | 6 +++--- simulators/ethereum/engine/helper/helper.go | 14 +++++++++----- simulators/ethereum/engine/test/env.go | 6 +++--- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go index d93104ecc0..991345809d 100644 --- a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go +++ b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go @@ -104,9 +104,9 @@ func (s HiveRPCEngineStarter) StartClient(T *hivesim.T, testContext context.Cont return nil, fmt.Errorf("Engine/Eth ports were never open for client: %v", err) } ec := NewHiveRPCEngineClient(c, enginePort, ethPort, jwtSecret, ttd, &helper.LoggingRoundTrip{ - T: T, - Hc: c, - Inner: http.DefaultTransport, + Logger: T, + ID: c.Container, + Inner: http.DefaultTransport, }) return ec, nil } diff --git a/simulators/ethereum/engine/helper/helper.go b/simulators/ethereum/engine/helper/helper.go index 164617df6d..6c417a4756 100644 --- a/simulators/ethereum/engine/helper/helper.go +++ b/simulators/ethereum/engine/helper/helper.go @@ -32,10 +32,14 @@ import ( // From ethereum/rpc: // LoggingRoundTrip writes requests and responses to the test log. +type LogF interface { + Logf(format string, values ...interface{}) +} + type LoggingRoundTrip struct { - T *hivesim.T - Hc *hivesim.Client - Inner http.RoundTripper + Logger LogF + ID string + Inner http.RoundTripper } func (rt *LoggingRoundTrip) RoundTrip(req *http.Request) (*http.Response, error) { @@ -45,7 +49,7 @@ func (rt *LoggingRoundTrip) RoundTrip(req *http.Request) (*http.Response, error) if err != nil { return nil, err } - rt.T.Logf(">> (%s) %s", rt.Hc.Container, bytes.TrimSpace(reqBytes)) + rt.Logger.Logf(">> (%s) %s", rt.ID, bytes.TrimSpace(reqBytes)) reqCopy := *req reqCopy.Body = ioutil.NopCloser(bytes.NewReader(reqBytes)) @@ -63,7 +67,7 @@ func (rt *LoggingRoundTrip) RoundTrip(req *http.Request) (*http.Response, error) } respCopy := *resp respCopy.Body = ioutil.NopCloser(bytes.NewReader(respBytes)) - rt.T.Logf("<< (%s) %s", rt.Hc.Container, bytes.TrimSpace(respBytes)) + rt.Logger.Logf("<< (%s) %s", rt.ID, bytes.TrimSpace(respBytes)) return &respCopy, nil } diff --git a/simulators/ethereum/engine/test/env.go b/simulators/ethereum/engine/test/env.go index 4221306b2f..00a1f1fe83 100644 --- a/simulators/ethereum/engine/test/env.go +++ b/simulators/ethereum/engine/test/env.go @@ -67,9 +67,9 @@ func Run(testSpec SpecInterface, ttd *big.Int, timeout time.Duration, t *hivesim // Create Engine client from main hivesim.Client to be used by tests ec := hive_rpc.NewHiveRPCEngineClient(c, globals.EnginePortHTTP, globals.EthPortHTTP, globals.DefaultJwtTokenSecretBytes, ttd, &helper.LoggingRoundTrip{ - T: t, - Hc: c, - Inner: http.DefaultTransport, + Logger: t, + ID: c.Container, + Inner: http.DefaultTransport, }) defer ec.Close() From 6f2d52c7979c2106bec75e0a5e3cbb603006c338 Mon Sep 17 00:00:00 2001 From: marioevz Date: Tue, 29 Nov 2022 15:41:30 -0600 Subject: [PATCH 24/49] clients/go-ethereum: add delve debugging --- clients/go-ethereum/Dockerfile | 7 ++++--- clients/go-ethereum/geth.sh | 9 +++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/clients/go-ethereum/Dockerfile b/clients/go-ethereum/Dockerfile index e702a44de2..debceeba20 100644 --- a/clients/go-ethereum/Dockerfile +++ b/clients/go-ethereum/Dockerfile @@ -13,8 +13,9 @@ FROM alpine:latest ARG branch=master # Build go-ethereum on the fly and delete all build tools afterwards +RUN echo https://dl-cdn.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories RUN \ - apk add --update bash curl jq go git make gcc musl-dev \ + apk add --update bash curl jq go delve git make gcc musl-dev \ ca-certificates linux-headers && \ git clone --depth 1 --branch withdrawals-timestamp https://github.com/lightclient/go-ethereum && \ (cd go-ethereum && make geth) && \ @@ -26,7 +27,7 @@ RUN \ > /version.json) && \ cp go-ethereum/build/bin/geth /usr/local/bin/geth && \ apk del go git make gcc musl-dev linux-headers && \ - rm -rf /go-ethereum && rm -rf /var/cache/apk/* + rm -rf /var/cache/apk/* RUN /usr/local/bin/geth console --exec 'console.log(admin.nodeInfo.name)' --maxpeers=0 --nodiscover --dev 2>/dev/null | head -1 > /version.txt @@ -43,7 +44,7 @@ RUN chmod +x /hive-bin/enode.sh ADD genesis.json /genesis.json # Export the usual networking ports to allow outside access to the node -EXPOSE 8545 8546 8547 8551 30303 30303/udp +EXPOSE 8545 8546 8547 8551 30303 30303/udp 40000 # Generate the ethash verification caches RUN \ diff --git a/clients/go-ethereum/geth.sh b/clients/go-ethereum/geth.sh index afe47ca6f5..59f944b9be 100644 --- a/clients/go-ethereum/geth.sh +++ b/clients/go-ethereum/geth.sh @@ -173,5 +173,10 @@ fi # Run the go-ethereum implementation with the requested flags. FLAGS="$FLAGS --nat=none" -echo "Running go-ethereum with flags $FLAGS" -$geth $FLAGS +if [ "$HIVE_CLIENT_DEBUGGING" != "" ]; then + echo "Running go-ethereum in debugger mode with flags $FLAGS" + "dlv" "--listen=:40000" "--headless=true" "--api-version=2" "--accept-multiclient" "--allow-non-terminal-interactive" "exec" $geth -- $FLAGS +else + echo "Running go-ethereum with flags $FLAGS" + $geth $FLAGS +fi \ No newline at end of file From e92ff01ae3e5f21ce7a3ae4e1722095bab652469 Mon Sep 17 00:00:00 2001 From: marioevz Date: Tue, 29 Nov 2022 18:17:06 -0600 Subject: [PATCH 25/49] simulator/ethereum/engine: Add txs to withdrawal tests --- .../engine/suites/withdrawals/tests.go | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index d30adcbace..bf2dd0f97f 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -142,6 +142,27 @@ var Tests = []test.SpecInterface{ }, // Sync Tests + WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account - No transactions", + About: ` + - Spawn a first client + - Go through withdrawals fork on Block 1 + - Withdraw to a single account 16 times each block for 8 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance + `, + TimeoutSeconds: 6000, + }, + WithdrawalsForkHeight: 1, + WithdrawalsBlockCount: 2, + WithdrawalsPerBlock: 16, + WithdrawableAccountCount: 1, + TransactionsPerBlock: common.Big0, + }, + SyncSteps: 1, + }, WithdrawalsSyncSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ @@ -179,6 +200,26 @@ var Tests = []test.SpecInterface{ }, SyncSteps: 1, }, + WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Sync after 2 blocks - Withdrawals on Block 2 - Single Withdrawal Account - No Transactions", + About: ` + - Spawn a first client + - Go through withdrawals fork on Block 2 + - Withdraw to a single account 16 times each block for 8 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync, which include syncing a pre-Withdrawals block, and verify withdrawn account's balance + `, + }, + WithdrawalsForkHeight: 2, + WithdrawalsBlockCount: 2, + WithdrawalsPerBlock: 16, + WithdrawableAccountCount: 1, + TransactionsPerBlock: common.Big0, + }, + SyncSteps: 1, + }, WithdrawalsSyncSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ @@ -490,6 +531,7 @@ type WithdrawalsBaseSpec struct { WithdrawableAccountCount uint64 // Number of accounts to withdraw to (round-robin) WithdrawalsHistory WithdrawalsHistory // Internal withdrawals history that keeps track of all withdrawals WithdrawAmounts []*big.Int // Amounts of withdrawn wei on each withdrawal (round-robin) + TransactionsPerBlock *big.Int // Amount of test transactions to include in withdrawal blocks SkipBaseVerifications bool // For code reuse of the base spec procedure } @@ -604,6 +646,13 @@ func (ws WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, star return nextWithdrawals, nextIndex } +func (ws WithdrawalsBaseSpec) GetTransactionCountPerPayload() uint64 { + if ws.TransactionsPerBlock == nil { + return 16 + } + return ws.TransactionsPerBlock.Uint64() +} + // Base test case execution procedure for withdrawals func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { // Create the withdrawals history object @@ -677,6 +726,13 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { // Send some withdrawals t.CLMock.NextWithdrawals, nextIndex = ws.GenerateWithdrawalsForBlock(nextIndex, startAccount) ws.WithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals + // Send some transactions + for i := uint64(0); i < ws.GetTransactionCountPerPayload(); i++ { + _, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, common.Big1, nil, t.TestTransactionType) + if err != nil { + t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) + } + } }, OnGetPayload: func() { // TODO: Send new payload with `withdrawals=null` and expect error @@ -893,6 +949,16 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { }, OnRequestNextPayload: func() { + // Send transactions to be included in the payload + for i := uint64(0); i < ws.GetTransactionCountPerPayload(); i++ { + tx, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, common.Big1, nil, t.TestTransactionType) + if err != nil { + t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) + } + // Error will be ignored here since the tx could have been already relayed + secondaryEngine.SendTransaction(t.TestContext, tx) + } + if t.CLMock.CurrentPayloadNumber >= ws.GetSidechainSplitHeight() { // Also request a payload from the sidechain fcU := beacon.ForkchoiceStateV1{ From fe7f0f5dbf1b6ba9e69a13e215df351fa49ef262 Mon Sep 17 00:00:00 2001 From: marioevz Date: Sun, 4 Dec 2022 22:54:56 -0600 Subject: [PATCH 26/49] simulators/ethereum/engine: Decrease withdrawals count --- simulators/ethereum/engine/suites/withdrawals/tests.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index bf2dd0f97f..707cda0f71 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -107,7 +107,7 @@ var Tests = []test.SpecInterface{ TimeoutSeconds: 120, }, WithdrawalsForkHeight: 1, - WithdrawalsBlockCount: 16, + WithdrawalsBlockCount: 4, WithdrawalsPerBlock: 1024, WithdrawableAccountCount: 1024, }, From cbb85dce7b03fdd60859bbd0622bffcb51730c43 Mon Sep 17 00:00:00 2001 From: marioevz Date: Tue, 6 Dec 2022 18:26:29 -0600 Subject: [PATCH 27/49] simulators/ethereum/engine: Withdrawals test change --- simulators/ethereum/engine/suites/withdrawals/tests.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 707cda0f71..04577a7e5d 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -145,7 +145,7 @@ var Tests = []test.SpecInterface{ WithdrawalsSyncSpec{ WithdrawalsBaseSpec: WithdrawalsBaseSpec{ Spec: test.Spec{ - Name: "Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account - No transactions", + Name: "Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account - No Transactions", About: ` - Spawn a first client - Go through withdrawals fork on Block 1 @@ -1094,6 +1094,9 @@ func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { nil, ) p.ExpectNoError() + if p.Response.PayloadStatus.Status == test.Invalid { + t.Fatalf("FAIL (%s): Primary client invalidated side chain", t.TestName) + } select { case <-t.TimeoutContext.Done(): t.Fatalf("FAIL (%s): Timeout waiting for sync", t.TestName) From c9e2628c64e2ac9a48a8a336b51654939fd2ed61 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 8 Dec 2022 10:39:03 -0600 Subject: [PATCH 28/49] simulators/ethereum/engine: Withdrawals, comments --- .../engine/suites/withdrawals/tests.go | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 04577a7e5d..baf2a09154 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -60,6 +60,9 @@ var Tests = []test.SpecInterface{ About: ` Tests the transition to the withdrawals fork after a single block has happened. + Block 1 is sent with invalid non-null withdrawals payload and + client is expected to respond 'INVALID' with the appropriate + 'latestValidHash'=='genesis.Hash'. `, }, WithdrawalsForkHeight: 2, // Genesis and Block 1 are Pre-Withdrawals @@ -67,6 +70,22 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 16, }, + WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Withdrawals Fork on Block 3", + About: ` + Tests the transition to the withdrawals fork after a single block + has happened. + Block 2 is sent with invalid non-null withdrawals payload and + client is expected to respond 'INVALID' with the appropriate + 'latestValidHash'=='block1.Hash'. + `, + }, + WithdrawalsForkHeight: 3, // Genesis, Block 1 and 2 are Pre-Withdrawals + WithdrawalsBlockCount: 1, + WithdrawalsPerBlock: 16, + }, + WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw to a single account", @@ -104,7 +123,7 @@ var Tests = []test.SpecInterface{ Make multiple withdrawals to 1024 different accounts. Execute many blocks this way. `, - TimeoutSeconds: 120, + TimeoutSeconds: 240, }, WithdrawalsForkHeight: 1, WithdrawalsBlockCount: 4, @@ -116,7 +135,7 @@ var Tests = []test.SpecInterface{ Spec: test.Spec{ Name: "Withdraw zero amount", About: ` - Make multiple withdrawals with amount==0. + Make multiple withdrawals where the amount withdrawn is 0. `, }, WithdrawalsForkHeight: 1, @@ -149,7 +168,7 @@ var Tests = []test.SpecInterface{ About: ` - Spawn a first client - Go through withdrawals fork on Block 1 - - Withdraw to a single account 16 times each block for 8 blocks + - Withdraw to a single account 16 times each block for 2 blocks - Spawn a secondary client and send FCUV2(head) - Wait for sync and verify withdrawn account's balance `, @@ -170,7 +189,7 @@ var Tests = []test.SpecInterface{ About: ` - Spawn a first client - Go through withdrawals fork on Block 1 - - Withdraw to a single account 16 times each block for 8 blocks + - Withdraw to a single account 16 times each block for 2 blocks - Spawn a secondary client and send FCUV2(head) - Wait for sync and verify withdrawn account's balance `, @@ -568,12 +587,12 @@ func AddUnconditionalBytecode(g *core.Genesis, start *big.Int, end *big.Int) { // Bytecode to unconditionally set a storage key g.Alloc[accountAddress] = core.GenesisAccount{ Code: []byte{ - 0x60, + 0x60, // PUSH1(0x01) 0x01, - 0x60, - 0x00, - 0x55, + 0x60, // PUSH1(0x00) 0x00, + 0x55, // SSTORE + 0x00, // STOP }, // sstore(0, 1) Nonce: 0, Balance: common.Big0, From d4b3142ab55f501eab4215fdd94db2003e0a2b56 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 8 Dec 2022 10:57:55 -0600 Subject: [PATCH 29/49] clients/erigon: Shanghai --- clients/erigon/Dockerfile | 2 +- clients/erigon/mapper.jq | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/erigon/Dockerfile b/clients/erigon/Dockerfile index 0d096ea4aa..3876b6c1c0 100644 --- a/clients/erigon/Dockerfile +++ b/clients/erigon/Dockerfile @@ -1,4 +1,4 @@ -ARG branch=devel +ARG branch=docker_withdrawals FROM thorax/erigon:$branch # The upstream erigon container uses a non-root user, but we need diff --git a/clients/erigon/mapper.jq b/clients/erigon/mapper.jq index 3c0da79ed6..c033bd22ce 100644 --- a/clients/erigon/mapper.jq +++ b/clients/erigon/mapper.jq @@ -51,5 +51,6 @@ def to_bool: "berlinBlock": env.HIVE_FORK_BERLIN|to_int, "londonBlock": env.HIVE_FORK_LONDON|to_int, "terminalTotalDifficulty": env.HIVE_TERMINAL_TOTAL_DIFFICULTY|to_int, + "shanghaiTime": env.HIVE_SHANGHAI_TIMESTAMP|to_int, }|remove_empty } From 5816a2c8f27d70fd8bd19d4f45da61514f68274a Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 12 Dec 2022 10:35:26 -0600 Subject: [PATCH 30/49] simulators/ethereum/engine: fix lvh in withdrawals --- simulators/ethereum/engine/suites/withdrawals/tests.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index baf2a09154..dddf5df32a 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -723,7 +723,8 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { } r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) r.ExpectStatus(test.Invalid) - r.ExpectLatestValidHash(&t.CLMock.LatestExecutedPayload.BlockHash) + expectedLvh := t.CLMock.LatestHeader.Hash() + r.ExpectLatestValidHash(&expectedLvh) }, OnNewPayloadBroadcast: func() { // We sent a pre-shanghai FCU. From e8da0c95a7832797dfadd713619e01b8330fb569 Mon Sep 17 00:00:00 2001 From: marioevz Date: Tue, 13 Dec 2022 14:26:48 -0600 Subject: [PATCH 31/49] simulators/ethereum/engine: go mod tidy --- simulators/ethereum/engine/go.mod | 1 + simulators/ethereum/engine/go.sum | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/simulators/ethereum/engine/go.mod b/simulators/ethereum/engine/go.mod index 049cfbf31d..2c3fd4d6a4 100644 --- a/simulators/ethereum/engine/go.mod +++ b/simulators/ethereum/engine/go.mod @@ -33,6 +33,7 @@ require ( github.com/graph-gophers/graphql-go v1.4.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect + github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.1 // indirect github.com/huin/goupnp v1.0.3 // indirect diff --git a/simulators/ethereum/engine/go.sum b/simulators/ethereum/engine/go.sum index 0b1f9a8f91..19d1e7b079 100644 --- a/simulators/ethereum/engine/go.sum +++ b/simulators/ethereum/engine/go.sum @@ -84,7 +84,6 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-ipa v0.0.0-20220523130400-f11357ae11c7/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -112,8 +111,6 @@ github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8E github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= -github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/ethereum/hive v0.0.0-20221123180504-f0f647240e9b h1:uWfa1x0lA7o5O6XgFxENIxAlevy9W5U6nAp79naD7e4= github.com/ethereum/hive v0.0.0-20221123180504-f0f647240e9b/go.mod h1:G8XiiUErpj6++yb93r6y6T1nWBh4Bgoi3QFwUSbzt/U= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -303,6 +300,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342 h1:lSsZMLsNkER1eVnYJ67cUhWcK82amzDPsVyAQ/JJ9p8= +github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= From b52fd134457a97139ae29c3e2a450c4ae38f169c Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 16 Dec 2022 09:22:02 -0600 Subject: [PATCH 32/49] simulators/ethereum/engine: Fix lvh check on genesis --- simulators/ethereum/engine/suites/withdrawals/tests.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index dddf5df32a..787e384c0b 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -723,8 +723,13 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { } r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) r.ExpectStatus(test.Invalid) - expectedLvh := t.CLMock.LatestHeader.Hash() - r.ExpectLatestValidHash(&expectedLvh) + if t.CLMock.LatestHeader.Number.Int64() > 0 { + // Only do this check when the latest valid payload is + // post-genesis, because genesis could be interpreted as a + // Proof-of-work block by some clients, which is not an error. + expectedLvh := t.CLMock.LatestHeader.Hash() + r.ExpectLatestValidHash(&expectedLvh) + } }, OnNewPayloadBroadcast: func() { // We sent a pre-shanghai FCU. From 3499b9fcc133fa0553efbfb3d210273bac042db4 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 19 Dec 2022 11:33:29 -0600 Subject: [PATCH 33/49] simulators/ethereum/engine: Fix long sync test --- .../engine/suites/withdrawals/tests.go | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 787e384c0b..fc8de125b1 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -275,6 +275,7 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 128, WithdrawalsPerBlock: 16, WithdrawableAccountCount: 1024, + SkipBaseVerifications: true, // Otherwise test runs too long }, SyncSteps: 1, }, @@ -765,63 +766,69 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { OnNewPayloadBroadcast: func() { // Check withdrawal addresses and verify withdrawal balances // have not yet been applied - for _, addr := range ws.WithdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { - // Test balance at `latest`, which should not yet have the - // withdrawal applied. - r := t.TestEngine.TestBalanceAt(addr, nil) - r.ExpectBalanceEqual( - ws.WithdrawalsHistory.GetExpectedAccountBalance( - addr, - t.CLMock.LatestExecutedPayload.Number-1), - ) + if !ws.SkipBaseVerifications { + for _, addr := range ws.WithdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { + // Test balance at `latest`, which should not yet have the + // withdrawal applied. + r := t.TestEngine.TestBalanceAt(addr, nil) + r.ExpectBalanceEqual( + ws.WithdrawalsHistory.GetExpectedAccountBalance( + addr, + t.CLMock.LatestExecutedPayload.Number-1), + ) + } } }, OnForkchoiceBroadcast: func() { // Check withdrawal addresses and verify withdrawal balances // have been applied - for _, addr := range ws.WithdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { - // Test balance at `latest`, which should not yet have the - // withdrawal applied. - r := t.TestEngine.TestBalanceAt(addr, nil) - r.ExpectBalanceEqual( - ws.WithdrawalsHistory.GetExpectedAccountBalance( - addr, - t.CLMock.LatestExecutedPayload.Number), + if !ws.SkipBaseVerifications { + for _, addr := range ws.WithdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { + // Test balance at `latest`, which should not yet have the + // withdrawal applied. + r := t.TestEngine.TestBalanceAt(addr, nil) + r.ExpectBalanceEqual( + ws.WithdrawalsHistory.GetExpectedAccountBalance( + addr, + t.CLMock.LatestExecutedPayload.Number), + ) + } + // Check the correct withdrawal root on `latest` block + r := t.TestEngine.TestBlockByNumber(nil) + expectedWithdrawalsRoot := types.DeriveSha( + ws.WithdrawalsHistory.GetWithdrawals(t.CLMock.LatestExecutedPayload.Number), + trie.NewStackTrie(nil), ) + r.ExpectWithdrawalsRoot(&expectedWithdrawalsRoot) } - // Check the correct withdrawal root on `latest` block - r := t.TestEngine.TestBlockByNumber(nil) - expectedWithdrawalsRoot := types.DeriveSha( - ws.WithdrawalsHistory.GetWithdrawals(t.CLMock.LatestExecutedPayload.Number), - trie.NewStackTrie(nil), - ) - r.ExpectWithdrawalsRoot(&expectedWithdrawalsRoot) }, }) // Iterate over balance history of withdrawn accounts using RPC and // check that the balances match expected values. // Also check one block before the withdrawal took place, verify that // withdrawal has not been updated. - for block := uint64(0); block <= t.CLMock.LatestExecutedPayload.Number; block++ { - ws.WithdrawalsHistory.VerifyWithdrawals(block, big.NewInt(int64(block)), t.TestEngine) - - // Check the correct withdrawal root on past blocks - r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) - var expectedWithdrawalsRoot *common.Hash = nil - if block >= ws.WithdrawalsForkHeight { - calcWithdrawalsRoot := types.DeriveSha( - ws.WithdrawalsHistory.GetWithdrawals(block), - trie.NewStackTrie(nil), - ) - expectedWithdrawalsRoot = &calcWithdrawalsRoot + if !ws.SkipBaseVerifications { + for block := uint64(0); block <= t.CLMock.LatestExecutedPayload.Number; block++ { + ws.WithdrawalsHistory.VerifyWithdrawals(block, big.NewInt(int64(block)), t.TestEngine) + + // Check the correct withdrawal root on past blocks + r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) + var expectedWithdrawalsRoot *common.Hash = nil + if block >= ws.WithdrawalsForkHeight { + calcWithdrawalsRoot := types.DeriveSha( + ws.WithdrawalsHistory.GetWithdrawals(block), + trie.NewStackTrie(nil), + ) + expectedWithdrawalsRoot = &calcWithdrawalsRoot + } + t.Logf("INFO (%s): Verifying withdrawals root on block %d (%s) to be %s", t.TestName, block, t.CLMock.ExecutedPayloadHistory[block].BlockHash, expectedWithdrawalsRoot) + r.ExpectWithdrawalsRoot(expectedWithdrawalsRoot) + } - t.Logf("INFO (%s): Verifying withdrawals root on block %d (%s) to be %s", t.TestName, block, t.CLMock.ExecutedPayloadHistory[block].BlockHash, expectedWithdrawalsRoot) - r.ExpectWithdrawalsRoot(expectedWithdrawalsRoot) + // Verify on `latest` + ws.WithdrawalsHistory.VerifyWithdrawals(t.CLMock.LatestExecutedPayload.Number, nil, t.TestEngine) } - - // Verify on `latest` - ws.WithdrawalsHistory.VerifyWithdrawals(t.CLMock.LatestExecutedPayload.Number, nil, t.TestEngine) } // Withdrawals sync spec: @@ -870,10 +877,8 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { } } } - - ws.WithdrawalsHistory.VerifyWithdrawals(t.CLMock.LatestHeader.Nonce.Uint64(), nil, secondaryEngineTest) - } + ws.WithdrawalsHistory.VerifyWithdrawals(t.CLMock.LatestHeader.Nonce.Uint64(), nil, secondaryEngineTest) } // Withdrawals re-org spec: From 4666b52e490626aa67e2c1f9ddf79a4fed0a9658 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 19 Dec 2022 12:01:52 -0600 Subject: [PATCH 34/49] simulators/ethereum/engine: Fix sync tests take 2 --- .../suites/withdrawals/spec_unit_test.go | 16 +-- .../engine/suites/withdrawals/tests.go | 118 +++++++++--------- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/spec_unit_test.go b/simulators/ethereum/engine/suites/withdrawals/spec_unit_test.go index 07eec23c97..73e3afb11b 100644 --- a/simulators/ethereum/engine/suites/withdrawals/spec_unit_test.go +++ b/simulators/ethereum/engine/suites/withdrawals/spec_unit_test.go @@ -7,7 +7,7 @@ import ( ) type BaseSpecExpected struct { - Spec WithdrawalsBaseSpec + Spec *WithdrawalsBaseSpec ExpectedBlockTimeIncrements uint64 ExpectedWithdrawalsTimestamp uint64 ExpectedPreWithdrawalsBlockCount uint64 @@ -16,14 +16,14 @@ type BaseSpecExpected struct { var baseSpecTestCases = []BaseSpecExpected{ { - Spec: WithdrawalsBaseSpec{}, + Spec: &WithdrawalsBaseSpec{}, ExpectedBlockTimeIncrements: 1, ExpectedWithdrawalsTimestamp: uint64(globals.GenesisTimestamp), ExpectedPreWithdrawalsBlockCount: 0, ExpectedTotalPayloadCount: 0, }, { - Spec: WithdrawalsBaseSpec{ + Spec: &WithdrawalsBaseSpec{ WithdrawalsBlockCount: 1, }, ExpectedBlockTimeIncrements: 1, @@ -61,7 +61,7 @@ type ReOrgSpecExpected struct { var reorgSpecTestCases = []ReOrgSpecExpected{ { Spec: WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ WithdrawalsForkHeight: 1, WithdrawalsBlockCount: 16, }, @@ -73,7 +73,7 @@ var reorgSpecTestCases = []ReOrgSpecExpected{ }, { Spec: WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ TimeIncrements: 12, WithdrawalsForkHeight: 4, WithdrawalsBlockCount: 1, @@ -87,7 +87,7 @@ var reorgSpecTestCases = []ReOrgSpecExpected{ }, { Spec: WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ TimeIncrements: 1, WithdrawalsForkHeight: 4, WithdrawalsBlockCount: 4, @@ -101,7 +101,7 @@ var reorgSpecTestCases = []ReOrgSpecExpected{ }, { Spec: WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ TimeIncrements: 1, WithdrawalsForkHeight: 8, WithdrawalsBlockCount: 8, @@ -115,7 +115,7 @@ var reorgSpecTestCases = []ReOrgSpecExpected{ }, { Spec: WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ TimeIncrements: 2, WithdrawalsForkHeight: 8, WithdrawalsBlockCount: 8, diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index fc8de125b1..a8dde570cb 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -29,7 +29,7 @@ var ( // List of all withdrawals tests var Tests = []test.SpecInterface{ - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork On Genesis", About: ` @@ -42,7 +42,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 16, }, - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 1", About: ` @@ -54,7 +54,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 16, }, - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 2", About: ` @@ -70,7 +70,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 16, }, - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 3", About: ` @@ -86,7 +86,7 @@ var Tests = []test.SpecInterface{ WithdrawalsPerBlock: 16, }, - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw to a single account", About: ` @@ -99,7 +99,7 @@ var Tests = []test.SpecInterface{ WithdrawableAccountCount: 1, }, - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw to two accounts", About: ` @@ -116,7 +116,7 @@ var Tests = []test.SpecInterface{ WithdrawableAccountCount: 2, }, - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw many accounts", About: ` @@ -131,7 +131,7 @@ var Tests = []test.SpecInterface{ WithdrawableAccountCount: 1024, }, - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdraw zero amount", About: ` @@ -148,7 +148,7 @@ var Tests = []test.SpecInterface{ }, }, - WithdrawalsBaseSpec{ + &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Empty Withdrawals", About: ` @@ -161,8 +161,8 @@ var Tests = []test.SpecInterface{ }, // Sync Tests - WithdrawalsSyncSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account - No Transactions", About: ` @@ -182,8 +182,8 @@ var Tests = []test.SpecInterface{ }, SyncSteps: 1, }, - WithdrawalsSyncSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Sync after 2 blocks - Withdrawals on Block 1 - Single Withdrawal Account", About: ` @@ -201,8 +201,8 @@ var Tests = []test.SpecInterface{ }, SyncSteps: 1, }, - WithdrawalsSyncSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Sync after 2 blocks - Withdrawals on Genesis - Single Withdrawal Account", About: ` @@ -219,8 +219,8 @@ var Tests = []test.SpecInterface{ }, SyncSteps: 1, }, - WithdrawalsSyncSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Sync after 2 blocks - Withdrawals on Block 2 - Single Withdrawal Account - No Transactions", About: ` @@ -239,8 +239,8 @@ var Tests = []test.SpecInterface{ }, SyncSteps: 1, }, - WithdrawalsSyncSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Sync after 2 blocks - Withdrawals on Block 2 - Single Withdrawal Account", About: ` @@ -258,8 +258,8 @@ var Tests = []test.SpecInterface{ }, SyncSteps: 1, }, - WithdrawalsSyncSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsSyncSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Sync after 128 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts", About: ` @@ -281,8 +281,8 @@ var Tests = []test.SpecInterface{ }, //Re-Org tests - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 1 - 1 Block Re-Org", About: ` @@ -298,8 +298,8 @@ var Tests = []test.SpecInterface{ ReOrgBlockCount: 1, ReOrgViaSync: false, }, - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload", About: ` @@ -316,8 +316,8 @@ var Tests = []test.SpecInterface{ ReOrgBlockCount: 8, ReOrgViaSync: false, }, - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 1 - 8 Block Re-Org, Sync", About: ` @@ -334,8 +334,8 @@ var Tests = []test.SpecInterface{ ReOrgBlockCount: 8, ReOrgViaSync: true, }, - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 8 - 10 Block Re-Org NewPayload", About: ` @@ -353,8 +353,8 @@ var Tests = []test.SpecInterface{ ReOrgBlockCount: 10, ReOrgViaSync: false, }, - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 8 - 10 Block Re-Org Sync", About: ` @@ -372,8 +372,8 @@ var Tests = []test.SpecInterface{ ReOrgBlockCount: 10, ReOrgViaSync: true, }, - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org", About: ` @@ -392,8 +392,8 @@ var Tests = []test.SpecInterface{ ReOrgViaSync: false, SidechainTimeIncrements: 2, }, - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org Sync", About: ` @@ -412,8 +412,8 @@ var Tests = []test.SpecInterface{ ReOrgViaSync: true, SidechainTimeIncrements: 2, }, - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org", About: ` @@ -433,8 +433,8 @@ var Tests = []test.SpecInterface{ ReOrgViaSync: false, SidechainTimeIncrements: 1, }, - WithdrawalsReorgSpec{ - WithdrawalsBaseSpec: WithdrawalsBaseSpec{ + &WithdrawalsReorgSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ Name: "Withdrawals Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org Sync", About: ` @@ -556,7 +556,7 @@ type WithdrawalsBaseSpec struct { } // Get the per-block timestamp increments configured for this test -func (ws WithdrawalsBaseSpec) GetBlockTimeIncrements() uint64 { +func (ws *WithdrawalsBaseSpec) GetBlockTimeIncrements() uint64 { if ws.TimeIncrements == 0 { return 1 } @@ -564,18 +564,18 @@ func (ws WithdrawalsBaseSpec) GetBlockTimeIncrements() uint64 { } // Timestamp delta between genesis and the withdrawals fork -func (ws WithdrawalsBaseSpec) GetWithdrawalsGenesisTimeDelta() uint64 { +func (ws *WithdrawalsBaseSpec) GetWithdrawalsGenesisTimeDelta() uint64 { return ws.WithdrawalsForkHeight * ws.GetBlockTimeIncrements() } // Calculates Shanghai fork timestamp given the amount of blocks that need to be // produced beforehand. -func (ws WithdrawalsBaseSpec) GetWithdrawalsForkTime() uint64 { +func (ws *WithdrawalsBaseSpec) GetWithdrawalsForkTime() uint64 { return uint64(globals.GenesisTimestamp) + ws.GetWithdrawalsGenesisTimeDelta() } // Generates the fork config, including withdrawals fork timestamp. -func (ws WithdrawalsBaseSpec) GetForkConfig() test.ForkConfig { +func (ws *WithdrawalsBaseSpec) GetForkConfig() test.ForkConfig { return test.ForkConfig{ ShanghaiTimestamp: big.NewInt(int64(ws.GetWithdrawalsForkTime())), } @@ -603,7 +603,7 @@ func AddUnconditionalBytecode(g *core.Genesis, start *big.Int, end *big.Int) { // Append the accounts we are going to withdraw to, which should also include // bytecode for testing purposes. -func (ws WithdrawalsBaseSpec) GetGenesis() *core.Genesis { +func (ws *WithdrawalsBaseSpec) GetGenesis() *core.Genesis { genesis := ws.Spec.GetGenesis() startAccount := big.NewInt(0x1000) endAccount := big.NewInt(0x1000 + int64(ws.GetWithdrawableAccountCount()) - 1) @@ -613,13 +613,13 @@ func (ws WithdrawalsBaseSpec) GetGenesis() *core.Genesis { // Changes the CL Mocker default time increments of 1 to the value specified // in the test spec. -func (ws WithdrawalsBaseSpec) ConfigureCLMock(cl *clmock.CLMocker) { +func (ws *WithdrawalsBaseSpec) ConfigureCLMock(cl *clmock.CLMocker) { cl.BlockTimestampIncrement = big.NewInt(int64(ws.GetBlockTimeIncrements())) } // Number of blocks to be produced (not counting genesis) before withdrawals // fork. -func (ws WithdrawalsBaseSpec) GetPreWithdrawalsBlockCount() uint64 { +func (ws *WithdrawalsBaseSpec) GetPreWithdrawalsBlockCount() uint64 { if ws.WithdrawalsForkHeight == 0 { return 0 } else { @@ -628,11 +628,11 @@ func (ws WithdrawalsBaseSpec) GetPreWithdrawalsBlockCount() uint64 { } // Number of payloads to be produced (pre and post withdrawals) during the entire test -func (ws WithdrawalsBaseSpec) GetTotalPayloadCount() uint64 { +func (ws *WithdrawalsBaseSpec) GetTotalPayloadCount() uint64 { return ws.GetPreWithdrawalsBlockCount() + ws.WithdrawalsBlockCount } -func (ws WithdrawalsBaseSpec) GetWithdrawableAccountCount() uint64 { +func (ws *WithdrawalsBaseSpec) GetWithdrawableAccountCount() uint64 { if ws.WithdrawableAccountCount == 0 { // Withdraw to 16 accounts by default return 16 @@ -641,7 +641,7 @@ func (ws WithdrawalsBaseSpec) GetWithdrawableAccountCount() uint64 { } // Generates a list of withdrawals based on current configuration -func (ws WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, startAccount *big.Int) (types.Withdrawals, uint64) { +func (ws *WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, startAccount *big.Int) (types.Withdrawals, uint64) { differentAccounts := ws.GetWithdrawableAccountCount() withdrawAmounts := ws.WithdrawAmounts if withdrawAmounts == nil { @@ -666,7 +666,7 @@ func (ws WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, star return nextWithdrawals, nextIndex } -func (ws WithdrawalsBaseSpec) GetTransactionCountPerPayload() uint64 { +func (ws *WithdrawalsBaseSpec) GetTransactionCountPerPayload() uint64 { if ws.TransactionsPerBlock == nil { return 16 } @@ -674,7 +674,7 @@ func (ws WithdrawalsBaseSpec) GetTransactionCountPerPayload() uint64 { } // Base test case execution procedure for withdrawals -func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { +func (ws *WithdrawalsBaseSpec) Execute(t *test.Env) { // Create the withdrawals history object ws.WithdrawalsHistory = make(WithdrawalsHistory) @@ -835,12 +835,12 @@ func (ws WithdrawalsBaseSpec) Execute(t *test.Env) { // Specifies a withdrawals test where the withdrawals happen and then a // client needs to sync and apply the withdrawals. type WithdrawalsSyncSpec struct { - WithdrawalsBaseSpec + *WithdrawalsBaseSpec SyncSteps int // Sync block chunks that will be passed as head through FCUs to the syncing client SyncShouldFail bool // } -func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { +func (ws *WithdrawalsSyncSpec) Execute(t *test.Env) { // Do the base withdrawal test first ws.WithdrawalsBaseSpec.Execute(t) @@ -878,7 +878,7 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { } } } - ws.WithdrawalsHistory.VerifyWithdrawals(t.CLMock.LatestHeader.Nonce.Uint64(), nil, secondaryEngineTest) + ws.WithdrawalsHistory.VerifyWithdrawals(t.CLMock.LatestHeader.Number.Uint64(), nil, secondaryEngineTest) } // Withdrawals re-org spec: @@ -886,28 +886,28 @@ func (ws WithdrawalsSyncSpec) Execute(t *test.Env) { // even to a point before withdrawals were enabled, or simply to a previous // withdrawals block. type WithdrawalsReorgSpec struct { - WithdrawalsBaseSpec + *WithdrawalsBaseSpec 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 } -func (ws WithdrawalsReorgSpec) GetSidechainSplitHeight() uint64 { +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 { +func (ws *WithdrawalsReorgSpec) GetSidechainBlockTimeIncrements() uint64 { if ws.SidechainTimeIncrements == 0 { return ws.GetBlockTimeIncrements() } return ws.SidechainTimeIncrements } -func (ws WithdrawalsReorgSpec) GetSidechainWithdrawalsForkHeight() uint64 { +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 { @@ -928,7 +928,7 @@ func (ws WithdrawalsReorgSpec) GetSidechainWithdrawalsForkHeight() uint64 { return ws.WithdrawalsForkHeight } -func (ws WithdrawalsReorgSpec) Execute(t *test.Env) { +func (ws *WithdrawalsReorgSpec) Execute(t *test.Env) { // Create the withdrawals history object ws.WithdrawalsHistory = make(WithdrawalsHistory) From 29a5c9a5f2ff4f303924dc2172e65ec96a09acf0 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 16 Dec 2022 10:45:10 -0600 Subject: [PATCH 35/49] simulators/ethereum/engine: Add blockValue to getPayload --- go.work.sum | 2 - simulators/ethereum/engine/client/engine.go | 2 +- .../client/hive_rpc/gen_getpayloadv2resp.go | 47 +++++++++++++++++++ .../engine/client/hive_rpc/hive_rpc.go | 41 ++++++++++++---- .../ethereum/engine/client/node/node.go | 8 ++-- simulators/ethereum/engine/clmock/clmock.go | 4 +- simulators/ethereum/engine/test/expect.go | 21 +++++---- 7 files changed, 100 insertions(+), 25 deletions(-) create mode 100644 simulators/ethereum/engine/client/hive_rpc/gen_getpayloadv2resp.go diff --git a/go.work.sum b/go.work.sum index f7f1dd59f3..04f870e551 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,6 +1,4 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio= -github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342 h1:lSsZMLsNkER1eVnYJ67cUhWcK82amzDPsVyAQ/JJ9p8= -github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= diff --git a/simulators/ethereum/engine/client/engine.go b/simulators/ethereum/engine/client/engine.go index 4e937a8fea..e56de1c2d0 100644 --- a/simulators/ethereum/engine/client/engine.go +++ b/simulators/ethereum/engine/client/engine.go @@ -29,7 +29,7 @@ type Engine interface { ForkchoiceUpdatedV2(ctx context.Context, fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) (api.ForkChoiceResponse, error) GetPayloadV1(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, error) - GetPayloadV2(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, error) + GetPayloadV2(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, *big.Int, error) NewPayloadV1(ctx context.Context, payload *api.ExecutableData) (api.PayloadStatusV1, error) NewPayloadV2(ctx context.Context, payload *api.ExecutableData) (api.PayloadStatusV1, error) diff --git a/simulators/ethereum/engine/client/hive_rpc/gen_getpayloadv2resp.go b/simulators/ethereum/engine/client/hive_rpc/gen_getpayloadv2resp.go new file mode 100644 index 0000000000..9be2c10b3f --- /dev/null +++ b/simulators/ethereum/engine/client/hive_rpc/gen_getpayloadv2resp.go @@ -0,0 +1,47 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package hive_rpc + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/beacon" +) + +var _ = (*getPayloadV2ResponseMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (g GetPayloadV2Response) MarshalJSON() ([]byte, error) { + type GetPayloadV2Response struct { + ExecutableData beacon.ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + } + var enc GetPayloadV2Response + enc.ExecutableData = g.ExecutableData + enc.BlockValue = (*hexutil.Big)(g.BlockValue) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (g *GetPayloadV2Response) UnmarshalJSON(input []byte) error { + type GetPayloadV2Response struct { + ExecutableData *beacon.ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + } + var dec GetPayloadV2Response + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ExecutableData == nil { + return errors.New("missing required field 'executionPayload' for GetPayloadV2Response") + } + g.ExecutableData = *dec.ExecutableData + if dec.BlockValue == nil { + return errors.New("missing required field 'blockValue' for GetPayloadV2Response") + } + g.BlockValue = (*big.Int)(dec.BlockValue) + return nil +} diff --git a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go index 991345809d..f0eea8413f 100644 --- a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go +++ b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go @@ -321,20 +321,45 @@ func (ec *HiveRPCEngineClient) ForkchoiceUpdatedV2(ctx context.Context, fcState return ec.ForkchoiceUpdated(ctx, 2, fcState, pAttributes) } -func (ec *HiveRPCEngineClient) GetPayload(ctx context.Context, version int, payloadId *api.PayloadID) (api.ExecutableData, error) { - var result api.ExecutableData - if err := ec.PrepareDefaultAuthCallToken(); err != nil { - return result, err +//go:generate go run github.com/fjl/gencodec -type GetPayloadV2Response -field-override getPayloadV2ResponseMarshaling -out gen_getpayloadv2resp.go +type GetPayloadV2Response struct { + ExecutableData api.ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *big.Int `json:"blockValue" gencodec:"required"` +} +type getPayloadV2ResponseMarshaling struct { + BlockValue *hexutil.Big +} + +func (ec *HiveRPCEngineClient) GetPayload(ctx context.Context, version int, payloadId *api.PayloadID) (api.ExecutableData, *big.Int, error) { + var ( + executableData api.ExecutableData + blockValue *big.Int + err error + rpcString = fmt.Sprintf("engine_getPayloadV%d", version) + ) + + if err = ec.PrepareDefaultAuthCallToken(); err != nil { + return executableData, nil, err } - err := ec.c.CallContext(ctx, &result, fmt.Sprintf("engine_getPayloadV%d", version), payloadId) - return result, err + + if version == 2 { + var response GetPayloadV2Response + err = ec.c.CallContext(ctx, &response, rpcString, payloadId) + executableData = response.ExecutableData + blockValue = response.BlockValue + } else { + err = ec.c.CallContext(ctx, &executableData, rpcString, payloadId) + } + + return executableData, blockValue, err } func (ec *HiveRPCEngineClient) GetPayloadV1(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, error) { - return ec.GetPayload(ctx, 1, payloadId) + ed, _, err := ec.GetPayload(ctx, 1, payloadId) + return ed, err } -func (ec *HiveRPCEngineClient) GetPayloadV2(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, error) { +func (ec *HiveRPCEngineClient) GetPayloadV2(ctx context.Context, payloadId *api.PayloadID) (api.ExecutableData, *big.Int, error) { return ec.GetPayload(ctx, 2, payloadId) } diff --git a/simulators/ethereum/engine/client/node/node.go b/simulators/ethereum/engine/client/node/node.go index baeb966dad..c98dec636b 100644 --- a/simulators/ethereum/engine/client/node/node.go +++ b/simulators/ethereum/engine/client/node/node.go @@ -726,12 +726,12 @@ func (n *GethNode) GetPayloadV1(ctx context.Context, payloadId *beacon.PayloadID return *p, err } -func (n *GethNode) GetPayloadV2(ctx context.Context, payloadId *beacon.PayloadID) (beacon.ExecutableData, error) { - p, err := n.api.GetPayloadV1(*payloadId) +func (n *GethNode) GetPayloadV2(ctx context.Context, payloadId *beacon.PayloadID) (beacon.ExecutableData, *big.Int, error) { + p, err := n.api.GetPayloadV2(*payloadId) if p == nil || err != nil { - return beacon.ExecutableData{}, err + return beacon.ExecutableData{}, nil, err } - return *p, err + return *p, common.Big0, err } // Eth JSON RPC diff --git a/simulators/ethereum/engine/clmock/clmock.go b/simulators/ethereum/engine/clmock/clmock.go index 1e1cadb75a..7e3b82daf5 100644 --- a/simulators/ethereum/engine/clmock/clmock.go +++ b/simulators/ethereum/engine/clmock/clmock.go @@ -60,6 +60,7 @@ type CLMocker struct { LatestHeadNumber *big.Int LatestHeader *types.Header LatestPayloadBuilt api.ExecutableData + LatestBlockValue *big.Int LatestPayloadAttributes api.PayloadAttributes LatestExecutedPayload api.ExecutableData LatestForkchoice api.ForkchoiceStateV1 @@ -332,10 +333,11 @@ func (cl *CLMocker) GetNextPayload() { ctx, cancel := context.WithTimeout(cl.TestContext, globals.RPCTimeout) defer cancel() if isShanghai(cl.LatestPayloadAttributes.Timestamp, cl.ShanghaiTimestamp) { - cl.LatestPayloadBuilt, err = cl.NextBlockProducer.GetPayloadV2(ctx, cl.NextPayloadID) + cl.LatestPayloadBuilt, cl.LatestBlockValue, err = cl.NextBlockProducer.GetPayloadV2(ctx, cl.NextPayloadID) } else { cl.LatestPayloadBuilt, err = cl.NextBlockProducer.GetPayloadV1(ctx, cl.NextPayloadID) + cl.LatestBlockValue = nil } if err != nil { cl.Fatalf("CLMocker: Could not getPayload (%v, %v): %v", cl.NextBlockProducer.ID(), cl.NextPayloadID, err) diff --git a/simulators/ethereum/engine/test/expect.go b/simulators/ethereum/engine/test/expect.go index 66272ed6e9..3f38f14631 100644 --- a/simulators/ethereum/engine/test/expect.go +++ b/simulators/ethereum/engine/test/expect.go @@ -272,8 +272,9 @@ func (exp *NewPayloadResponseExpectObject) ExpectLatestValidHash(lvh *common.Has // GetPayloadV1 type GetPayloadResponseExpectObject struct { *ExpectEnv - Payload api.ExecutableData - Error error + Payload api.ExecutableData + BlockValue *big.Int + Error error } func (tec *TestEngineClient) TestEngineGetPayloadV1(payloadID *api.PayloadID) *GetPayloadResponseExpectObject { @@ -281,20 +282,22 @@ func (tec *TestEngineClient) TestEngineGetPayloadV1(payloadID *api.PayloadID) *G defer cancel() payload, err := tec.Engine.GetPayloadV1(ctx, payloadID) return &GetPayloadResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, - Payload: payload, - Error: err, + ExpectEnv: &ExpectEnv{tec.Env}, + Payload: payload, + BlockValue: nil, + Error: err, } } func (tec *TestEngineClient) TestEngineGetPayloadV2(payloadID *api.PayloadID) *GetPayloadResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() - payload, err := tec.Engine.GetPayloadV2(ctx, payloadID) + payload, blockValue, err := tec.Engine.GetPayloadV2(ctx, payloadID) return &GetPayloadResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, - Payload: payload, - Error: err, + ExpectEnv: &ExpectEnv{tec.Env}, + Payload: payload, + BlockValue: blockValue, + Error: err, } } From 3c2c437cfbb4ac508fa93c51a45beff275c85266 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 13 Jan 2023 12:15:34 -0600 Subject: [PATCH 36/49] clients/go-ethereum: update shanghai config string --- clients/go-ethereum/mapper.jq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/go-ethereum/mapper.jq b/clients/go-ethereum/mapper.jq index 87d899453c..bbad21c66a 100644 --- a/clients/go-ethereum/mapper.jq +++ b/clients/go-ethereum/mapper.jq @@ -54,6 +54,6 @@ def to_bool: "londonBlock": env.HIVE_FORK_LONDON|to_int, "mergeForkBlock": env.HIVE_MERGE_BLOCK_ID|to_int, "terminalTotalDifficulty": env.HIVE_TERMINAL_TOTAL_DIFFICULTY|to_int, - "shanghaiBlock": env.HIVE_SHANGHAI_TIMESTAMP|to_int, + "shanghaiTime": env.HIVE_SHANGHAI_TIMESTAMP|to_int, }|remove_empty } From e4b4de2b6fae1557969b8c4274798e487f3465a2 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 13 Jan 2023 12:32:14 -0600 Subject: [PATCH 37/49] simulators/ethereum/engine: withdrawals, wei to gwei --- go.work.sum | 7 +++ .../client/hive_rpc/gen_getpayloadv2resp.go | 47 ------------------- .../engine/client/hive_rpc/hive_rpc.go | 13 +---- .../ethereum/engine/client/node/node.go | 2 +- simulators/ethereum/engine/go.mod | 7 ++- simulators/ethereum/engine/go.sum | 17 +++---- .../ethereum/engine/suites/engine/tests.go | 4 +- .../engine/suites/withdrawals/tests.go | 23 +++++---- 8 files changed, 36 insertions(+), 84 deletions(-) delete mode 100644 simulators/ethereum/engine/client/hive_rpc/gen_getpayloadv2resp.go diff --git a/go.work.sum b/go.work.sum index 04f870e551..531125f18e 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,10 @@ +cloud.google.com/go/bigtable v1.2.0 h1:F4cCmA4nuV84V5zYQ3MKY+M1Cw1avHDuf3S/LcZPA9c= +github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db h1:nxAtV4VajJDhKysp2kdcJZsq8Ss1xSA0vZTkVHHJd0E= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/c-bata/go-prompt v0.2.2 h1:uyKRz6Z6DUyj49QVijyM339UJV9yhbr70gESwbNU3e0= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio= diff --git a/simulators/ethereum/engine/client/hive_rpc/gen_getpayloadv2resp.go b/simulators/ethereum/engine/client/hive_rpc/gen_getpayloadv2resp.go deleted file mode 100644 index 9be2c10b3f..0000000000 --- a/simulators/ethereum/engine/client/hive_rpc/gen_getpayloadv2resp.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package hive_rpc - -import ( - "encoding/json" - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/beacon" -) - -var _ = (*getPayloadV2ResponseMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (g GetPayloadV2Response) MarshalJSON() ([]byte, error) { - type GetPayloadV2Response struct { - ExecutableData beacon.ExecutableData `json:"executionPayload" gencodec:"required"` - BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` - } - var enc GetPayloadV2Response - enc.ExecutableData = g.ExecutableData - enc.BlockValue = (*hexutil.Big)(g.BlockValue) - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (g *GetPayloadV2Response) UnmarshalJSON(input []byte) error { - type GetPayloadV2Response struct { - ExecutableData *beacon.ExecutableData `json:"executionPayload" gencodec:"required"` - BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` - } - var dec GetPayloadV2Response - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.ExecutableData == nil { - return errors.New("missing required field 'executionPayload' for GetPayloadV2Response") - } - g.ExecutableData = *dec.ExecutableData - if dec.BlockValue == nil { - return errors.New("missing required field 'blockValue' for GetPayloadV2Response") - } - g.BlockValue = (*big.Int)(dec.BlockValue) - return nil -} diff --git a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go index f0eea8413f..4346d2d93c 100644 --- a/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go +++ b/simulators/ethereum/engine/client/hive_rpc/hive_rpc.go @@ -321,15 +321,6 @@ func (ec *HiveRPCEngineClient) ForkchoiceUpdatedV2(ctx context.Context, fcState return ec.ForkchoiceUpdated(ctx, 2, fcState, pAttributes) } -//go:generate go run github.com/fjl/gencodec -type GetPayloadV2Response -field-override getPayloadV2ResponseMarshaling -out gen_getpayloadv2resp.go -type GetPayloadV2Response struct { - ExecutableData api.ExecutableData `json:"executionPayload" gencodec:"required"` - BlockValue *big.Int `json:"blockValue" gencodec:"required"` -} -type getPayloadV2ResponseMarshaling struct { - BlockValue *hexutil.Big -} - func (ec *HiveRPCEngineClient) GetPayload(ctx context.Context, version int, payloadId *api.PayloadID) (api.ExecutableData, *big.Int, error) { var ( executableData api.ExecutableData @@ -343,9 +334,9 @@ func (ec *HiveRPCEngineClient) GetPayload(ctx context.Context, version int, payl } if version == 2 { - var response GetPayloadV2Response + var response api.ExecutableDataV2 err = ec.c.CallContext(ctx, &response, rpcString, payloadId) - executableData = response.ExecutableData + executableData = *response.ExecutionPayload blockValue = response.BlockValue } else { err = ec.c.CallContext(ctx, &executableData, rpcString, payloadId) diff --git a/simulators/ethereum/engine/client/node/node.go b/simulators/ethereum/engine/client/node/node.go index c98dec636b..03f43e7c6e 100644 --- a/simulators/ethereum/engine/client/node/node.go +++ b/simulators/ethereum/engine/client/node/node.go @@ -731,7 +731,7 @@ func (n *GethNode) GetPayloadV2(ctx context.Context, payloadId *beacon.PayloadID if p == nil || err != nil { return beacon.ExecutableData{}, nil, err } - return *p, common.Big0, err + return *p.ExecutionPayload, p.BlockValue, err } // Eth JSON RPC diff --git a/simulators/ethereum/engine/go.mod b/simulators/ethereum/engine/go.mod index 2c3fd4d6a4..9fe0831b81 100644 --- a/simulators/ethereum/engine/go.mod +++ b/simulators/ethereum/engine/go.mod @@ -16,11 +16,12 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deckarep/golang-set v1.8.0 // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/deepmap/oapi-codegen v1.12.4 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/fjl/memsize v0.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/go-kit/kit v0.10.0 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect @@ -32,7 +33,6 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/graph-gophers/graphql-go v1.4.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect - github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.1 // indirect @@ -52,7 +52,6 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/tsdb v0.10.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect - github.com/rjeczalik/notify v0.9.2 // indirect github.com/rs/cors v1.8.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect @@ -73,4 +72,4 @@ require ( gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect ) -replace github.com/ethereum/go-ethereum => github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342 +replace github.com/ethereum/go-ethereum => github.com/marioevz/go-ethereum v1.10.14-0.20230113164816-9cfc6ec5a607 diff --git a/simulators/ethereum/engine/go.sum b/simulators/ethereum/engine/go.sum index 19d1e7b079..fdc05ecbe5 100644 --- a/simulators/ethereum/engine/go.sum +++ b/simulators/ethereum/engine/go.sum @@ -89,8 +89,8 @@ github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhr github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= -github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= @@ -121,8 +121,9 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -236,8 +237,6 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= @@ -300,11 +299,11 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342 h1:lSsZMLsNkER1eVnYJ67cUhWcK82amzDPsVyAQ/JJ9p8= -github.com/lightclient/go-ethereum v1.10.10-0.20221108134600-102fb324d342/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/marioevz/go-ethereum v1.10.14-0.20230113164816-9cfc6ec5a607 h1:3RAUX8WNcojZXfBGkqmJ0UdjASXl0uz+H5qSGDPNik0= +github.com/marioevz/go-ethereum v1.10.14-0.20230113164816-9cfc6ec5a607/go.mod h1:n7VlOgCwYheLB/mi+V8ni2yf8K2qM3N9WAmalxkhk+c= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -423,8 +422,6 @@ github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= -github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= @@ -591,7 +588,6 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -626,6 +622,7 @@ golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/simulators/ethereum/engine/suites/engine/tests.go b/simulators/ethereum/engine/suites/engine/tests.go index c3e6f592f3..e5341ff331 100644 --- a/simulators/ethereum/engine/suites/engine/tests.go +++ b/simulators/ethereum/engine/suites/engine/tests.go @@ -2221,7 +2221,7 @@ func (spec InvalidMissingAncestorReOrgSpec) GenerateSync() func(*test.Env) { ctx, cancel := context.WithTimeout(t.TestContext, globals.RPCTimeout) defer cancel() - p := api.BlockToExecutableData(altChainPayloads[i]) + p := api.BlockToExecutableData(altChainPayloads[i], common.Big0).ExecutionPayload status, err := secondaryClient.NewPayloadV1(ctx, p) if err != nil { t.Fatalf("FAIL (%s): TEST ISSUE - Unable to send new payload: %v", t.TestName, err) @@ -2284,7 +2284,7 @@ func (spec InvalidMissingAncestorReOrgSpec) GenerateSync() func(*test.Env) { } // If we are syncing through p2p, we need to keep polling until the client syncs the missing payloads for { - r := t.TestEngine.TestEngineNewPayloadV1(api.BlockToExecutableData(altChainPayloads[n])) + r := t.TestEngine.TestEngineNewPayloadV1(api.BlockToExecutableData(altChainPayloads[n], common.Big0).ExecutionPayload) t.Logf("INFO (%s): Response from main client: %v", t.TestName, r.Status) s := t.TestEngine.TestEngineForkchoiceUpdatedV1(&api.ForkchoiceStateV1{ HeadBlockHash: altChainPayloads[n].Hash(), diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index a8dde570cb..540dd2667d 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -142,9 +142,9 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 1, WithdrawalsPerBlock: 64, WithdrawableAccountCount: 2, - WithdrawAmounts: []*big.Int{ - common.Big0, - common.Big1, + WithdrawAmounts: []uint64{ + 0, + 1, }, }, @@ -457,6 +457,11 @@ var Tests = []test.SpecInterface{ // TODO: REORG SYNC WHERE SYNCED BLOCKS HAVE WITHDRAWALS BEFORE TIME } +// Helper types to convert gwei into wei more easily +func WeiAmount(w *types.Withdrawal) *big.Int { + return new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(1e9)) +} + // Helper structure used to keep history of the amounts withdrawn to each test account. type WithdrawalsHistory map[uint64]types.Withdrawals @@ -468,7 +473,7 @@ func (wh WithdrawalsHistory) GetExpectedAccountBalance(account common.Address, b if withdrawals, ok := wh[b]; ok && withdrawals != nil { for _, withdrawal := range withdrawals { if withdrawal.Address == account { - balance.Add(balance, withdrawal.Amount) + balance.Add(balance, WeiAmount(withdrawal)) } } } @@ -506,9 +511,9 @@ func (wh WithdrawalsHistory) GetWithdrawnAccounts(blockHeight uint64) map[common if withdrawals, ok := wh[block]; ok && withdrawals != nil { for _, withdrawal := range withdrawals { if currentBalance, ok2 := accounts[withdrawal.Address]; ok2 { - currentBalance.Add(currentBalance, withdrawal.Amount) + currentBalance.Add(currentBalance, WeiAmount(withdrawal)) } else { - accounts[withdrawal.Address] = new(big.Int).Set(withdrawal.Amount) + accounts[withdrawal.Address] = new(big.Int).Set(WeiAmount(withdrawal)) } } } @@ -550,7 +555,7 @@ type WithdrawalsBaseSpec struct { WithdrawalsPerBlock uint64 // Number of withdrawals per block WithdrawableAccountCount uint64 // Number of accounts to withdraw to (round-robin) WithdrawalsHistory WithdrawalsHistory // Internal withdrawals history that keeps track of all withdrawals - WithdrawAmounts []*big.Int // Amounts of withdrawn wei on each withdrawal (round-robin) + WithdrawAmounts []uint64 // Amounts of withdrawn wei on each withdrawal (round-robin) TransactionsPerBlock *big.Int // Amount of test transactions to include in withdrawal blocks SkipBaseVerifications bool // For code reuse of the base spec procedure } @@ -645,8 +650,8 @@ func (ws *WithdrawalsBaseSpec) GenerateWithdrawalsForBlock(nextIndex uint64, sta differentAccounts := ws.GetWithdrawableAccountCount() withdrawAmounts := ws.WithdrawAmounts if withdrawAmounts == nil { - withdrawAmounts = []*big.Int{ - big.NewInt(1), + withdrawAmounts = []uint64{ + 1, } } From 03e086ea45779e8e564061df98890e2a1a32420b Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 13 Jan 2023 18:39:37 -0600 Subject: [PATCH 38/49] simulators/ethereum/engine: fix genesis block --- simulators/ethereum/engine/suites/engine/tests.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/simulators/ethereum/engine/suites/engine/tests.go b/simulators/ethereum/engine/suites/engine/tests.go index e5341ff331..b123a1c097 100644 --- a/simulators/ethereum/engine/suites/engine/tests.go +++ b/simulators/ethereum/engine/suites/engine/tests.go @@ -1196,7 +1196,7 @@ var Tests = []test.Spec{ // Invalid Terminal Block in ForkchoiceUpdated: Client must reject ForkchoiceUpdated directives if the referenced HeadBlockHash does not meet the TTD requirement. func invalidTerminalBlockForkchoiceUpdated(t *test.Env) { - gblock := helper.LoadGenesisBlock(t.ClientFiles["/genesis.json"]) + gblock := t.Genesis.ToBlock() forkchoiceState := api.ForkchoiceStateV1{ HeadBlockHash: gblock.Hash(), @@ -1218,7 +1218,7 @@ func invalidTerminalBlockForkchoiceUpdated(t *test.Env) { // Invalid GetPayload Under PoW: Client must reject GetPayload directives under PoW. func invalidGetPayloadUnderPoW(t *test.Env) { - gblock := helper.LoadGenesisBlock(t.ClientFiles["/genesis.json"]) + gblock := t.Genesis.ToBlock() // We start in PoW and try to get an invalid Payload, which should produce an error but nothing should be disrupted. r := t.TestEngine.TestEngineGetPayloadV1(&api.PayloadID{1, 2, 3, 4, 5, 6, 7, 8}) r.ExpectError() @@ -1229,7 +1229,7 @@ func invalidGetPayloadUnderPoW(t *test.Env) { // Invalid Terminal Block in NewPayload: Client must reject NewPayload directives if the referenced ParentHash does not meet the TTD requirement. func invalidTerminalBlockNewPayload(t *test.Env) { - gblock := helper.LoadGenesisBlock(t.ClientFiles["/genesis.json"]) + gblock := t.Genesis.ToBlock() // Create a dummy payload to send in the NewPayload call payload := api.ExecutableData{ @@ -1496,7 +1496,7 @@ func preTTDFinalizedBlockHash(t *test.Env) { t.CLMock.ProduceBlocks(5, clmock.BlockProcessCallbacks{}) // Send the Genesis block as forkchoice - gblock := helper.LoadGenesisBlock(t.ClientFiles["/genesis.json"]) + gblock := t.Genesis.ToBlock() r := t.TestEngine.TestEngineForkchoiceUpdatedV1(&api.ForkchoiceStateV1{ HeadBlockHash: gblock.Hash(), @@ -2479,7 +2479,7 @@ func blockStatusFinalizedBlock(t *test.Env) { func safeFinalizedCanonicalChain(t *test.Env) { // Wait until this client catches up with latest PoS Block t.CLMock.WaitForTTD() - gblock := helper.LoadGenesisBlock(t.ClientFiles["/genesis.json"]) + gblock := t.Genesis.ToBlock() // At the merge we need to have head equal to the last PoW block, and safe/finalized must return error h := t.TestEngine.TestHeaderByNumber(Head) From ab04d00d859eb64fb8d16df8d303e8364d1fac06 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 13 Jan 2023 18:40:29 -0600 Subject: [PATCH 39/49] simulators/ethereum/engine: implement error code expect check --- simulators/ethereum/engine/test/expect.go | 225 ++++++++++++++-------- 1 file changed, 147 insertions(+), 78 deletions(-) diff --git a/simulators/ethereum/engine/test/expect.go b/simulators/ethereum/engine/test/expect.go index 3f38f14631..b063db4c40 100644 --- a/simulators/ethereum/engine/test/expect.go +++ b/simulators/ethereum/engine/test/expect.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" api "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/hive/simulators/ethereum/engine/client" "github.com/ethereum/hive/simulators/ethereum/engine/globals" ) @@ -53,10 +54,14 @@ func StatusesToString(statuses []PayloadStatus) []string { // Test Engine API Helper Structs type ExpectEnv struct { *Env + ExpectationDescription string } func (e ExpectEnv) Fatalf(format string, values ...interface{}) { PrintExpectStack(e.Env) + if e.ExpectationDescription != "" { + e.Env.Logf("DEBUG (%s): %s", e.TestName, e.ExpectationDescription) + } e.Env.Fatalf(format, values...) } @@ -67,7 +72,7 @@ type TestEngineClient struct { func NewTestEngineClient(t *Env, ec client.EngineClient) *TestEngineClient { return &TestEngineClient{ - ExpectEnv: &ExpectEnv{t}, + ExpectEnv: &ExpectEnv{Env: t}, Engine: ec, } } @@ -76,33 +81,42 @@ func NewTestEngineClient(t *Env, ec client.EngineClient) *TestEngineClient { type ForkchoiceResponseExpectObject struct { *ExpectEnv - Response api.ForkChoiceResponse - Version int - Error error + Response api.ForkChoiceResponse + Version int + Error error + ErrorCode int } func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV1(fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) *ForkchoiceResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() resp, err := tec.Engine.ForkchoiceUpdatedV1(ctx, fcState, pAttributes) - return &ForkchoiceResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &ForkchoiceResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Response: resp, Version: 1, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (tec *TestEngineClient) TestEngineForkchoiceUpdatedV2(fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes) *ForkchoiceResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() resp, err := tec.Engine.ForkchoiceUpdatedV2(ctx, fcState, pAttributes) - return &ForkchoiceResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &ForkchoiceResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Response: resp, Version: 2, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (tec *TestEngineClient) TestEngineForkchoiceUpdated(fcState *api.ForkchoiceStateV1, pAttributes *api.PayloadAttributes, version int) *ForkchoiceResponseExpectObject { @@ -131,9 +145,9 @@ func (exp *ForkchoiceResponseExpectObject) ExpectError() { } func (exp *ForkchoiceResponseExpectObject) ExpectErrorCode(code int) { - // TODO: Actually check error code - if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineForkchoiceUpdatedV%d: response=%v", exp.TestName, exp.Version, exp.Response) + exp.ExpectError() + if exp.ErrorCode != code { + exp.Fatalf("FAIL (%s): Expected error code on EngineForkchoiceUpdatedV%d: want=%d, got=%d", exp.TestName, exp.Version, code, exp.ErrorCode) } } @@ -174,36 +188,45 @@ func (exp *ForkchoiceResponseExpectObject) ExpectPayloadID(pid *api.PayloadID) { type NewPayloadResponseExpectObject struct { *ExpectEnv - Payload *api.ExecutableData - Status api.PayloadStatusV1 - Version int - Error error + Payload *api.ExecutableData + Status api.PayloadStatusV1 + Version int + Error error + ErrorCode int } func (tec *TestEngineClient) TestEngineNewPayloadV1(payload *api.ExecutableData) *NewPayloadResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() status, err := tec.Engine.NewPayloadV1(ctx, payload) - return &NewPayloadResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &NewPayloadResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Payload: payload, Status: status, Version: 1, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (tec *TestEngineClient) TestEngineNewPayloadV2(payload *api.ExecutableData) *NewPayloadResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() status, err := tec.Engine.NewPayloadV2(ctx, payload) - return &NewPayloadResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &NewPayloadResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Payload: payload, Status: status, Version: 2, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (tec *TestEngineClient) TestEngineNewPayload(payload *api.ExecutableData, version int) *NewPayloadResponseExpectObject { @@ -231,9 +254,9 @@ func (exp *NewPayloadResponseExpectObject) ExpectError() { } func (exp *NewPayloadResponseExpectObject) ExpectErrorCode(code int) { - // TODO: Actually check error code - if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineNewPayloadV%d: status=%v, payload=%s", exp.TestName, exp.Version, exp.Status, exp.PayloadJson()) + exp.ExpectError() + if exp.ErrorCode != code { + exp.Fatalf("FAIL (%s): Expected error code on EngineNewPayloadV%d: want=%d, got=%d", exp.TestName, exp.Version, code, exp.ErrorCode) } } @@ -274,31 +297,43 @@ type GetPayloadResponseExpectObject struct { *ExpectEnv Payload api.ExecutableData BlockValue *big.Int + Version int Error error + ErrorCode int } func (tec *TestEngineClient) TestEngineGetPayloadV1(payloadID *api.PayloadID) *GetPayloadResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() payload, err := tec.Engine.GetPayloadV1(ctx, payloadID) - return &GetPayloadResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &GetPayloadResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Payload: payload, + Version: 1, BlockValue: nil, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (tec *TestEngineClient) TestEngineGetPayloadV2(payloadID *api.PayloadID) *GetPayloadResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() payload, blockValue, err := tec.Engine.GetPayloadV2(ctx, payloadID) - return &GetPayloadResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &GetPayloadResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Payload: payload, + Version: 2, BlockValue: blockValue, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (tec *TestEngineClient) TestEngineGetPayload(payloadID *api.PayloadID, version int) *GetPayloadResponseExpectObject { @@ -310,69 +345,74 @@ func (tec *TestEngineClient) TestEngineGetPayload(payloadID *api.PayloadID, vers func (exp *GetPayloadResponseExpectObject) ExpectNoError() { if exp.Error != nil { - exp.Fatalf("FAIL (%s): Expected no error on EngineGetPayloadV1: error=%v", exp.TestName, exp.Error) + exp.Fatalf("FAIL (%s): Expected no error on EngineGetPayloadV%d: error=%v", exp.TestName, exp.Version, exp.Error) } } func (exp *GetPayloadResponseExpectObject) ExpectError() { if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineGetPayloadV1: payload=%v", exp.TestName, exp.Payload) + exp.Fatalf("FAIL (%s): Expected error on EngineGetPayloadV%d: payload=%v", exp.TestName, exp.Version, exp.Payload) } } func (exp *GetPayloadResponseExpectObject) ExpectErrorCode(code int) { - // TODO: Actually check error code - if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on EngineGetPayloadV1: payload=%v", exp.TestName, exp.Payload) + exp.ExpectError() + if exp.ErrorCode != code { + exp.Fatalf("FAIL (%s): Expected error code on EngineGetPayloadV%d: want=%d, got=%d", exp.TestName, exp.Version, code, exp.ErrorCode) } } func (exp *GetPayloadResponseExpectObject) ExpectPayloadParentHash(expectedParentHash common.Hash) { exp.ExpectNoError() if exp.Payload.ParentHash != expectedParentHash { - exp.Fatalf("FAIL (%s): Unexpected parent hash for payload on EngineGetPayloadV1: %v, expected=%v", exp.TestName, exp.Payload.ParentHash, expectedParentHash) + exp.Fatalf("FAIL (%s): Unexpected parent hash for payload on EngineGetPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Payload.ParentHash, expectedParentHash) } } func (exp *GetPayloadResponseExpectObject) ExpectPayloadBlockNumber(expectedBlockNumber uint64) { exp.ExpectNoError() if exp.Payload.Number != expectedBlockNumber { - exp.Fatalf("FAIL (%s): Unexpected block number for payload on EngineGetPayloadV1: %v, expected=%v", exp.TestName, exp.Payload.Number, expectedBlockNumber) + exp.Fatalf("FAIL (%s): Unexpected block number for payload on EngineGetPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Payload.Number, expectedBlockNumber) } } func (exp *GetPayloadResponseExpectObject) ExpectPrevRandao(expectedPrevRandao common.Hash) { exp.ExpectNoError() if exp.Payload.Random != expectedPrevRandao { - exp.Fatalf("FAIL (%s): Unexpected prevRandao for payload on EngineGetPayloadV1: %v, expected=%v", exp.TestName, exp.Payload.Random, expectedPrevRandao) + exp.Fatalf("FAIL (%s): Unexpected prevRandao for payload on EngineGetPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Payload.Random, expectedPrevRandao) } } func (exp *GetPayloadResponseExpectObject) ExpectTimestamp(expectedTimestamp uint64) { exp.ExpectNoError() if exp.Payload.Timestamp != expectedTimestamp { - exp.Fatalf("FAIL (%s): Unexpected timestamp for payload on EngineGetPayloadV1: %v, expected=%v", exp.TestName, exp.Payload.Timestamp, expectedTimestamp) + exp.Fatalf("FAIL (%s): Unexpected timestamp for payload on EngineGetPayloadV%d: %v, expected=%v", exp.TestName, exp.Version, exp.Payload.Timestamp, expectedTimestamp) } } // BlockNumber type BlockNumberResponseExpectObject struct { *ExpectEnv - Call string - Number uint64 - Error error + Call string + Number uint64 + Error error + ErrorCode int } func (tec *TestEngineClient) TestBlockNumber() *BlockNumberResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() number, err := tec.Engine.BlockNumber(ctx) - return &BlockNumberResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &BlockNumberResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Call: "BlockNumber", Number: number, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (exp *BlockNumberResponseExpectObject) ExpectNoError() { @@ -392,21 +432,26 @@ func (exp *BlockNumberResponseExpectObject) ExpectNumber(number uint64) { type HeaderResponseExpectObject struct { *ExpectEnv - Call string - Header *types.Header - Error error + Call string + Header *types.Header + Error error + ErrorCode int } func (tec *TestEngineClient) TestHeaderByNumber(number *big.Int) *HeaderResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() header, err := tec.Engine.HeaderByNumber(ctx, number) - return &HeaderResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &HeaderResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Call: "HeaderByNumber", Header: header, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (exp *HeaderResponseExpectObject) ExpectNoError() { @@ -422,9 +467,9 @@ func (exp *HeaderResponseExpectObject) ExpectError() { } func (exp *HeaderResponseExpectObject) ExpectErrorCode(code int) { - // TODO: Actually check error code - if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on %s: header=%v", exp.TestName, exp.Call, exp.Header) + exp.ExpectError() + if exp.ErrorCode != code { + exp.Fatalf("FAIL (%s): Expected error code on %s: want=%d, got=%d", exp.TestName, exp.Call, code, exp.ErrorCode) } } @@ -439,33 +484,42 @@ func (exp *HeaderResponseExpectObject) ExpectHash(expHash common.Hash) { type BlockResponseExpectObject struct { *ExpectEnv - Call string - Block *types.Block - Error error + Call string + Block *types.Block + Error error + ErrorCode int } func (tec *TestEngineClient) TestBlockByNumber(number *big.Int) *BlockResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() block, err := tec.Engine.BlockByNumber(ctx, number) - return &BlockResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &BlockResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Call: "BlockByNumber", Block: block, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (tec *TestEngineClient) TestBlockByHash(hash common.Hash) *BlockResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() block, err := tec.Engine.BlockByHash(ctx, hash) - return &BlockResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &BlockResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Call: "BlockByHash", Block: block, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (exp *BlockResponseExpectObject) ExpectError() { @@ -475,9 +529,9 @@ func (exp *BlockResponseExpectObject) ExpectError() { } func (exp *BlockResponseExpectObject) ExpectErrorCode(code int) { - // TODO: Actually check error code - if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on %s: block=%v", exp.TestName, exp.Call, exp.Block) + exp.ExpectError() + if exp.ErrorCode != code { + exp.Fatalf("FAIL (%s): Expected error code on %s: want=%d, got=%d", exp.TestName, exp.Call, code, exp.ErrorCode) } } @@ -528,25 +582,30 @@ func (exp *BlockResponseExpectObject) ExpectCoinbase(expCoinbase common.Address) type BalanceResponseExpectObject struct { *ExpectEnv - Call string - Account common.Address - Block *big.Int - Balance *big.Int - Error error + Call string + Account common.Address + Block *big.Int + Balance *big.Int + Error error + ErrorCode int } func (tec *TestEngineClient) TestBalanceAt(account common.Address, number *big.Int) *BalanceResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() balance, err := tec.Engine.BalanceAt(ctx, account, number) - return &BalanceResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &BalanceResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Call: "BalanceAt", Account: account, Balance: balance, Block: number, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (exp *BalanceResponseExpectObject) ExpectNoError() { @@ -574,21 +633,26 @@ func (exp *BalanceResponseExpectObject) ExpectBalanceEqual(expBalance *big.Int) type StorageResponseExpectObject struct { *ExpectEnv - Call string - Storage []byte - Error error + Call string + Storage []byte + Error error + ErrorCode int } func (tec *TestEngineClient) TestStorageAt(account common.Address, key common.Hash, number *big.Int) *StorageResponseExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() storage, err := tec.Engine.StorageAt(ctx, account, key, number) - return &StorageResponseExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &StorageResponseExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Call: "StorageAt", Storage: storage, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (exp *StorageResponseExpectObject) ExpectNoError() { @@ -617,21 +681,26 @@ func (exp *StorageResponseExpectObject) ExpectStorageEqual(expStorage common.Has // Transaction Receipt type TransactionReceiptExpectObject struct { *ExpectEnv - Call string - Receipt *types.Receipt - Error error + Call string + Receipt *types.Receipt + Error error + ErrorCode int } func (tec *TestEngineClient) TestTransactionReceipt(txHash common.Hash) *TransactionReceiptExpectObject { ctx, cancel := context.WithTimeout(tec.TestContext, globals.RPCTimeout) defer cancel() receipt, err := tec.Engine.TransactionReceipt(ctx, txHash) - return &TransactionReceiptExpectObject{ - ExpectEnv: &ExpectEnv{tec.Env}, + ret := &TransactionReceiptExpectObject{ + ExpectEnv: &ExpectEnv{Env: tec.Env}, Call: "TransactionReceipt", Receipt: receipt, Error: err, } + if err, ok := err.(rpc.Error); ok { + ret.ErrorCode = err.ErrorCode() + } + return ret } func (exp *TransactionReceiptExpectObject) ExpectError() { @@ -641,9 +710,9 @@ func (exp *TransactionReceiptExpectObject) ExpectError() { } func (exp *TransactionReceiptExpectObject) ExpectErrorCode(code int) { - // TODO: Actually check error code - if exp.Error == nil { - exp.Fatalf("FAIL (%s): Expected error on %s: block=%v", exp.TestName, exp.Call, exp.Receipt) + exp.ExpectError() + if exp.ErrorCode != code { + exp.Fatalf("FAIL (%s): Expected error code on %s: want=%d, got=%d", exp.TestName, exp.Call, code, exp.ErrorCode) } } From d09322168d5ac44cd3b613a646484a386ae5f69e Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 13 Jan 2023 18:41:20 -0600 Subject: [PATCH 40/49] simulators/ethereum/engine: withdrawals, expect errors on invalid version --- .../ethereum/engine/helper/withdrawals.go | 19 +++ .../engine/suites/withdrawals/tests.go | 152 +++++++++++------- 2 files changed, 117 insertions(+), 54 deletions(-) create mode 100644 simulators/ethereum/engine/helper/withdrawals.go diff --git a/simulators/ethereum/engine/helper/withdrawals.go b/simulators/ethereum/engine/helper/withdrawals.go new file mode 100644 index 0000000000..6397ee2c9d --- /dev/null +++ b/simulators/ethereum/engine/helper/withdrawals.go @@ -0,0 +1,19 @@ +package helper + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + EmptyWithdrawalsRootHash = &types.EmptyRootHash +) + +func ComputeWithdrawalsRoot(ws types.Withdrawals) common.Hash { + // Using RLP root but might change to ssz + return types.DeriveSha( + ws, + trie.NewStackTrie(nil), + ) +} diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 540dd2667d..4ddf6413a2 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -2,6 +2,8 @@ package suite_withdrawals import ( + "encoding/json" + "fmt" "math/big" "time" @@ -9,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/hive/simulators/ethereum/engine/client/hive_rpc" "github.com/ethereum/hive/simulators/ethereum/engine/clmock" "github.com/ethereum/hive/simulators/ethereum/engine/globals" @@ -18,10 +19,11 @@ import ( ) var ( - Head *big.Int // Nil - Pending = big.NewInt(-2) - Finalized = big.NewInt(-3) - Safe = big.NewInt(-4) + Head *big.Int // Nil + Pending = big.NewInt(-2) + Finalized = big.NewInt(-3) + Safe = big.NewInt(-4) + InvalidParamsError = -32602 ) // Execution specification reference: @@ -275,7 +277,6 @@ var Tests = []test.SpecInterface{ WithdrawalsBlockCount: 128, WithdrawalsPerBlock: 16, WithdrawableAccountCount: 1024, - SkipBaseVerifications: true, // Otherwise test runs too long }, SyncSteps: 1, }, @@ -692,56 +693,71 @@ func (ws *WithdrawalsBaseSpec) Execute(t *test.Env) { // Genesis should not contain `withdrawalsRoot` either r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectationDescription = ` + Requested "latest" block expecting genesis to contain + withdrawalRoot=nil, because genesis.timestamp < shanghaiTime + ` r.ExpectWithdrawalsRoot(nil) } else { // Genesis is post shanghai, it should contain EmptyWithdrawalsRoot r := t.TestEngine.TestBlockByNumber(nil) - r.ExpectWithdrawalsRoot(&types.EmptyRootHash) + r.ExpectationDescription = ` + Requested "latest" block expecting genesis to contain + withdrawalRoot=EmptyTrieRoot, because genesis.timestamp >= shanghaiTime + ` + r.ExpectWithdrawalsRoot(helper.EmptyWithdrawalsRootHash) } // Produce any blocks necessary to reach withdrawals fork t.CLMock.ProduceBlocks(int(ws.GetPreWithdrawalsBlockCount()), clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { - // Try to send a ForkchoiceUpdatedV2 with non-null - // withdrawals before Shanghai - r := t.TestEngine.TestEngineForkchoiceUpdatedV2( - &beacon.ForkchoiceStateV1{ - HeadBlockHash: t.CLMock.LatestHeader.Hash(), - }, - &beacon.PayloadAttributes{ - Timestamp: t.CLMock.LatestHeader.Time + 1, - Random: common.Hash{}, - SuggestedFeeRecipient: common.Address{}, - Withdrawals: make(types.Withdrawals, 0), - }, - ) - r.ExpectError() + if !ws.SkipBaseVerifications { + // Try to send a ForkchoiceUpdatedV2 with non-null + // withdrawals before Shanghai + r := t.TestEngine.TestEngineForkchoiceUpdatedV2( + &beacon.ForkchoiceStateV1{ + HeadBlockHash: t.CLMock.LatestHeader.Hash(), + }, + &beacon.PayloadAttributes{ + Timestamp: t.CLMock.LatestHeader.Time + 1, + Random: common.Hash{}, + SuggestedFeeRecipient: common.Address{}, + Withdrawals: make(types.Withdrawals, 0), + }, + ) + r.ExpectationDescription = "Sent pre-shanghai fcu using EngineForkchoiceUpdatedV2+Withdrawals, error is expected" + r.ExpectErrorCode(InvalidParamsError) + } }, OnGetPayload: func() { - // Send produced payload but try to include non-nil - // `withdrawals`, it should fail. - emptyWithdrawalsList := make(types.Withdrawals, 0) - payloadPlusWithdrawals, err := helper.CustomizePayload(&t.CLMock.LatestPayloadBuilt, &helper.CustomPayloadData{ - Withdrawals: emptyWithdrawalsList, - }) - if err != nil { - t.Fatalf("Unable to append withdrawals: %v", err) - } - r := t.TestEngine.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) - r.ExpectStatus(test.Invalid) - if t.CLMock.LatestHeader.Number.Int64() > 0 { - // Only do this check when the latest valid payload is - // post-genesis, because genesis could be interpreted as a - // Proof-of-work block by some clients, which is not an error. - expectedLvh := t.CLMock.LatestHeader.Hash() - r.ExpectLatestValidHash(&expectedLvh) + if !ws.SkipBaseVerifications { + // Send produced payload but try to include non-nil + // `withdrawals`, it should fail. + emptyWithdrawalsList := make(types.Withdrawals, 0) + payloadPlusWithdrawals, err := helper.CustomizePayload(&t.CLMock.LatestPayloadBuilt, &helper.CustomPayloadData{ + Withdrawals: emptyWithdrawalsList, + }) + if err != nil { + t.Fatalf("Unable to append withdrawals: %v", err) + } + r := t.TestEngine.TestEngineNewPayloadV2(payloadPlusWithdrawals) + r.ExpectationDescription = "Sent pre-shanghai payload using NewPayloadV2+Withdrawals, error is expected" + r.ExpectErrorCode(InvalidParamsError) } }, OnNewPayloadBroadcast: func() { - // We sent a pre-shanghai FCU. - // Keep expecting `nil` until Shanghai. - r := t.TestEngine.TestBlockByNumber(nil) - r.ExpectWithdrawalsRoot(nil) + if !ws.SkipBaseVerifications { + // We sent a pre-shanghai FCU. + // Keep expecting `nil` until Shanghai. + r := t.TestEngine.TestBlockByNumber(nil) + r.ExpectationDescription = fmt.Sprintf(` + Requested "latest" block expecting block to contain + withdrawalRoot=nil, because (block %d).timestamp < shanghaiTime + `, + t.CLMock.LatestPayloadBuilt.Number, + ) + r.ExpectWithdrawalsRoot(nil) + } }, }) @@ -775,12 +791,20 @@ func (ws *WithdrawalsBaseSpec) Execute(t *test.Env) { for _, addr := range ws.WithdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { // Test balance at `latest`, which should not yet have the // withdrawal applied. + expectedAccountBalance := ws.WithdrawalsHistory.GetExpectedAccountBalance( + addr, + t.CLMock.LatestExecutedPayload.Number-1) r := t.TestEngine.TestBalanceAt(addr, nil) - r.ExpectBalanceEqual( - ws.WithdrawalsHistory.GetExpectedAccountBalance( - addr, - t.CLMock.LatestExecutedPayload.Number-1), + r.ExpectationDescription = fmt.Sprintf(` + Requested balance for account %s on "latest" block + after engine_newPayloadV2, expecting balance to be equal + to value on previous block (%d), since the new payload + has not yet been applied. + `, + addr, + t.CLMock.LatestExecutedPayload.Number-1, ) + r.ExpectBalanceEqual(expectedAccountBalance) } } }, @@ -789,9 +813,18 @@ func (ws *WithdrawalsBaseSpec) Execute(t *test.Env) { // have been applied if !ws.SkipBaseVerifications { for _, addr := range ws.WithdrawalsHistory.GetAddressesWithdrawnOnBlock(t.CLMock.LatestExecutedPayload.Number) { - // Test balance at `latest`, which should not yet have the + // Test balance at `latest`, which should have the // withdrawal applied. r := t.TestEngine.TestBalanceAt(addr, nil) + r.ExpectationDescription = fmt.Sprintf(` + Requested balance for account %s on "latest" block + after engine_forkchoiceUpdatedV2, expecting balance to + be equal to value on latest payload (%d), since the new payload + has not yet been applied. + `, + addr, + t.CLMock.LatestExecutedPayload.Number, + ) r.ExpectBalanceEqual( ws.WithdrawalsHistory.GetExpectedAccountBalance( addr, @@ -800,10 +833,16 @@ func (ws *WithdrawalsBaseSpec) Execute(t *test.Env) { } // Check the correct withdrawal root on `latest` block r := t.TestEngine.TestBlockByNumber(nil) - expectedWithdrawalsRoot := types.DeriveSha( - ws.WithdrawalsHistory.GetWithdrawals(t.CLMock.LatestExecutedPayload.Number), - trie.NewStackTrie(nil), + expectedWithdrawalsRoot := helper.ComputeWithdrawalsRoot( + ws.WithdrawalsHistory.GetWithdrawals( + t.CLMock.LatestExecutedPayload.Number, + ), ) + jsWithdrawals, _ := json.MarshalIndent(ws.WithdrawalsHistory.GetWithdrawals(t.CLMock.LatestExecutedPayload.Number), "", " ") + r.ExpectationDescription = fmt.Sprintf(` + Requested "latest" block after engine_forkchoiceUpdatedV2, + to verify withdrawalsRoot with the following withdrawals: + %s`, jsWithdrawals) r.ExpectWithdrawalsRoot(&expectedWithdrawalsRoot) } }, @@ -820,13 +859,17 @@ func (ws *WithdrawalsBaseSpec) Execute(t *test.Env) { r := t.TestEngine.TestBlockByNumber(big.NewInt(int64(block))) var expectedWithdrawalsRoot *common.Hash = nil if block >= ws.WithdrawalsForkHeight { - calcWithdrawalsRoot := types.DeriveSha( + calcWithdrawalsRoot := helper.ComputeWithdrawalsRoot( ws.WithdrawalsHistory.GetWithdrawals(block), - trie.NewStackTrie(nil), ) expectedWithdrawalsRoot = &calcWithdrawalsRoot } - t.Logf("INFO (%s): Verifying withdrawals root on block %d (%s) to be %s", t.TestName, block, t.CLMock.ExecutedPayloadHistory[block].BlockHash, expectedWithdrawalsRoot) + jsWithdrawals, _ := json.MarshalIndent(ws.WithdrawalsHistory.GetWithdrawals(block), "", " ") + r.ExpectationDescription = fmt.Sprintf(` + Requested block %d to verify withdrawalsRoot with the + following withdrawals: + %s`, block, jsWithdrawals) + r.ExpectWithdrawalsRoot(expectedWithdrawalsRoot) } @@ -846,7 +889,8 @@ type WithdrawalsSyncSpec struct { } func (ws *WithdrawalsSyncSpec) Execute(t *test.Env) { - // Do the base withdrawal test first + // Do the base withdrawal test first, skipping base verifications + ws.WithdrawalsBaseSpec.SkipBaseVerifications = true ws.WithdrawalsBaseSpec.Execute(t) // Spawn a secondary client which will need to sync to the primary client From 1e3784c90ef93e1f24035414e16f0f5f7e6e392d Mon Sep 17 00:00:00 2001 From: marioevz Date: Wed, 18 Jan 2023 11:33:55 -0600 Subject: [PATCH 41/49] simulators/ethereum/engine: Withdrawals readme --- simulators/ethereum/engine/README.md | 5 +- .../engine/suites/withdrawals/README.md | 81 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 simulators/ethereum/engine/suites/withdrawals/README.md diff --git a/simulators/ethereum/engine/README.md b/simulators/ethereum/engine/README.md index 22f633037c..b13234ac06 100644 --- a/simulators/ethereum/engine/README.md +++ b/simulators/ethereum/engine/README.md @@ -455,4 +455,7 @@ Invalid token error is expected. - Positive time drift, within limit, correct secret: Engine API call where the `iat` claim contains a positive time drift smaller than the maximum threshold, and the secret to calculate the token is correct. -No error is expected. \ No newline at end of file +No error is expected. + +## Engine API Shanghai Upgrade Tests: +See [withdrawals](suites/withdrawals/README.md). \ No newline at end of file diff --git a/simulators/ethereum/engine/suites/withdrawals/README.md b/simulators/ethereum/engine/suites/withdrawals/README.md new file mode 100644 index 0000000000..8505531c0c --- /dev/null +++ b/simulators/ethereum/engine/suites/withdrawals/README.md @@ -0,0 +1,81 @@ +# Shanghai Withdrawals Testing + +This test suite verifies behavior of the Engine API on each client after the Shanghai upgrade, according to the specification defined at: +- https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md + +## Shanghai Fork +- Genesis Shanghai: Tests the withdrawals fork happening since genesis (e.g. on a testnet). +- Shanghai Fork on Block 1: Tests the withdrawals fork happening directly after genesis. +- Shanghai Fork on Block 2: Tests the transition to the withdrawals fork after a single block has happened. Block 1 is sent with invalid non-null withdrawals payload and client is expected to respond with the appropriate error. +- Shanghai Fork on Block 3: Tests the transition to the withdrawals fork after two blocks have happened. Block 2 is sent with invalid non-null withdrawals payload (both in `engine_newPayloadV2` and the attributes of `engine_forkchoiceUpdatedV2`) and client is expected to respond with the appropriate error. + +## Withdrawals +- Withdraw to a single account: Make multiple withdrawals to a single account. +- Withdraw to two accounts: Make multiple withdrawals to two different accounts, repeated in round-robin. Reasoning: There might be a difference in implementation when an account appears multiple times in the withdrawals list but the list is not in ordered sequence. +- Withdraw to many accounts: Make multiple withdrawals to 1024 different accounts. Execute many blocks this way. +- Withdraw zero amount: Make multiple withdrawals where the amount withdrawn is 0. +- Empty Withdrawals: Produce withdrawals block with zero withdrawals. + +## Sync to Shanghai +- Sync after 2 blocks - Shanghai on Block 1 - Single Withdrawal Account - No Transactions: + - Spawn a first client + - Go through Shanghai fork on Block 1 + - Withdraw to a single account 16 times each block for 2 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance +- Sync after 2 blocks - Shanghai on Block 1 - Single Withdrawal Account: + - Spawn a first client + - Go through Shanghai fork on Block 1 + - Withdraw to a single account 16 times each block for 2 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance +- Sync after 2 blocks - Shanghai on Genesis - Single Withdrawal Account: + - Spawn a first client with Shanghai on Genesis + - Withdraw to a single account 16 times each block for 8 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance +- Sync after 2 blocks - Shanghai on Block 2 - Multiple Withdrawal Accounts - No Transactions: + - Spawn a first client + - Go through Shanghai fork on Block 2 + - Withdraw to 16 accounts each block for 2 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance +- Sync after 2 blocks - Shanghai on Block 2 - Multiple Withdrawal Accounts: + - Spawn a first client + - Go through Shanghai fork on Block 2 + - Withdraw to 16 accounts each block for 2 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance +- Sync after 128 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts: + - Spawn a first client + - Go through Shanghai fork on Block 2 + - Withdraw to many accounts 16 times each block for 128 blocks + - Spawn a secondary client and send FCUV2(head) + - Wait for sync and verify withdrawn account's balance +- [NOT IMPLEMENTED] Error Syncing Null Withdrawals Block after Shanghai: + - Spawn a first client with configuration `shanghaiTime==2` + - Send Block 1 with `timestamp==1` + - Go through Shanghai fork on Block 2 (`timestamp==2`) + - Withdraw to a single account 16 times each block for 2 blocks + - Spawn a secondary client with configuration `shanghaiTime==1` and send FCUV2(Block 2) + - Wait for sync and verify that `engine_newPayload` and `engine_forkchoiceUpdated` return `INVALID` for Block 2. + +## Shanghai Fork Re-Org Tests +- Shanghai Fork on Block 1 - 16 Post-Fork Blocks - 1 Block Re-Org via NewPayload: + - Spawn a two clients `A` and `B` + - Go through Shanghai fork on Block 1 + - Produce 15 blocks on both clients `A` and `B`, withdrawing to 16 accounts each block + - Produce a canonical chain `A` block 16 on client `A`, withdrawing to the same 16 accounts + - Produce a side chain `B` block 16 on client `B`, withdrawing to different 16 accounts + - Send `NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify the re-org was correctly applied along with the withdrawal balances. +- Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload + + +## Max Initcode Tests +- [NOT IMPLEMENTED] Transactions exceeding max initcode should be immediately rejected: + - Send two transactions with valid nonces each + - `TxA`, a smart contract creating transaction with an initcode length equal to MAX_INITCODE_SIZE ([EIP-3860](https://eips.ethereum.org/EIPS/eip-3860)) + - `TxB`, a smart contract creating transaction with an initcode length equal to MAX_INITCODE_SIZE+1. + - Verify that `TxB` returns error on `eth_sendRawTransaction` and also should be absend from the transaction pool of the client + - Request a new payload from the client and verify that the payload built only includes `TxA`, and `TxB` is not included, nor the contract it could create is present in the `stateRoot`. \ No newline at end of file From 42c28eb749410f784c2adb89b8ff601adb9e273a Mon Sep 17 00:00:00 2001 From: marioevz Date: Wed, 18 Jan 2023 15:14:11 -0600 Subject: [PATCH 42/49] simulators/ethereum/engine: update withdrawals readme --- .../engine/suites/withdrawals/README.md | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/README.md b/simulators/ethereum/engine/suites/withdrawals/README.md index 8505531c0c..8adf841976 100644 --- a/simulators/ethereum/engine/suites/withdrawals/README.md +++ b/simulators/ethereum/engine/suites/withdrawals/README.md @@ -61,6 +61,7 @@ This test suite verifies behavior of the Engine API on each client after the Sha - Wait for sync and verify that `engine_newPayload` and `engine_forkchoiceUpdated` return `INVALID` for Block 2. ## Shanghai Fork Re-Org Tests + - Shanghai Fork on Block 1 - 16 Post-Fork Blocks - 1 Block Re-Org via NewPayload: - Spawn a two clients `A` and `B` - Go through Shanghai fork on Block 1 @@ -69,8 +70,78 @@ This test suite verifies behavior of the Engine API on each client after the Sha - Produce a side chain `B` block 16 on client `B`, withdrawing to different 16 accounts - Send `NewPayload(B[16])+FcU(B[16])` to client `A` - Verify the re-org was correctly applied along with the withdrawal balances. -- Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload +- Shanghai Fork on Block 1 - 8 Block Re-Org NewPayload + - Spawn a two clients `A` and `B` + - Go through Shanghai fork on Block 1 + - Produce 8 blocks on both clients `A` and `B`, withdrawing to 16 accounts each block + - Produce canonical chain `A` blocks 9-16 on client `A`, withdrawing to the same 16 accounts + - Produce side chain `B` blocks 9-16 on client `B`, withdrawing to different 16 accounts + - Send `NewPayload(B[9])+FcU(B[9])..NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify the re-org was correctly applied along with the withdrawal balances. + +- Shanghai Fork on Block 1 - 8 Block Re-Org Sync + - Spawn a two clients `A` and `B` + - Go through Shanghai fork on Block 1 + - Produce 8 blocks on both clients `A` and `B`, withdrawing to 16 accounts each block + - Produce canonical chain `A` blocks 9-16 on client `A`, withdrawing to the same 16 accounts + - Produce side chain `B` blocks 9-16 on client `B`, withdrawing to different 16 accounts + - Send `NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify client `A` syncs side chain blocks from client `B` re-org was correctly applied along with the withdrawal balances. + +- Shanghai Fork on Block 8 - 10 Block Re-Org NewPayload + - Spawn a two clients `A` and `B` + - Produce 6 blocks on both clients `A` and `B` + - Produce canonical chain `A` blocks 7-16 on client `A` + - Produce side chain `B` blocks 7-16 on client `B` + - Shanghai fork occurs on blocks `A[8]` and `B[8]` + - Send `NewPayload(B[7])+FcU(B[7])..NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify the re-org was correctly applied along with the withdrawal balances. + +- Shanghai Fork on Block 8 - 10 Block Re-Org Sync + - Spawn a two clients `A` and `B` + - Produce 6 blocks on both clients `A` and `B` + - Produce canonical chain `A` blocks 7-16 on client `A` + - Produce side chain `B` blocks 7-16 on client `B` + - Shanghai fork occurs on blocks `A[8]` and `B[8]` + - Send `NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify client `A` syncs side chain blocks from client `B` re-org was correctly applied along with the withdrawal balances. + +- Shanghai Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org NewPayload + - Spawn a two clients `A` and `B` + - Produce 6 blocks on both clients `A` and `B` + - Produce canonical chain `A` blocks 7-16 on client `A` with timestamp increments of 1. + - Produce side chain `B` blocks 7-16 on client `B` with timestamp increments of 2 + - Shanghai fork occurs on blocks `A[8]` and `B[7]` + - Send `NewPayload(B[7])+FcU(B[7])..NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify the re-org was correctly applied along with the withdrawal balances. + +- Shanghai Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org Sync + - Spawn a two clients `A` and `B` + - Produce 6 blocks on both clients `A` and `B` + - Produce canonical chain `A` blocks 7-16 on client `A` with timestamp increments of 1. + - Produce side chain `B` blocks 7-16 on client `B` with timestamp increments of 2 + - Shanghai fork occurs on blocks `A[8]` and `B[7]` + - Send `NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify client `A` syncs side chain blocks from client `B` re-org was correctly applied along with the withdrawal balances. + +- Shanghai Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org NewPayload + - Spawn a two clients `A` and `B` + - Produce 6 blocks on both clients `A` and `B`, with timestamp increments of 2 + - Produce canonical chain `A` blocks 7-16 on client `A` with timestamp increments of 2. + - Produce side chain `B` blocks 7-16 on client `B` with timestamp increments of 1 + - Shanghai fork occurs on blocks `A[8]` and `B[9]` + - Send `NewPayload(B[7])+FcU(B[7])..NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify the re-org was correctly applied along with the withdrawal balances. + +- Shanghai Fork on Canonical Block 8 / Side Block 9 - 10 Block Re-Org Sync + - Spawn a two clients `A` and `B` + - Produce 6 blocks on both clients `A` and `B`, with timestamp increments of 2 + - Produce canonical chain `A` blocks 7-16 on client `A` with timestamp increments of 2. + - Produce side chain `B` blocks 7-16 on client `B` with timestamp increments of 1 + - Shanghai fork occurs on blocks `A[8]` and `B[9]` + - Send `NewPayload(B[16])+FcU(B[16])` to client `A` + - Verify client `A` syncs side chain blocks from client `B` re-org was correctly applied along with the withdrawal balances. ## Max Initcode Tests - [NOT IMPLEMENTED] Transactions exceeding max initcode should be immediately rejected: From 62a69bf20d65cb99a64369089f33acf070c0c49e Mon Sep 17 00:00:00 2001 From: marioevz Date: Wed, 18 Jan 2023 15:14:36 -0600 Subject: [PATCH 43/49] simulators/ethereum/engine: update test parameter, description --- .../engine/suites/withdrawals/tests.go | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 4ddf6413a2..31e6a4cd5b 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -63,8 +63,7 @@ var Tests = []test.SpecInterface{ Tests the transition to the withdrawals fork after a single block has happened. Block 1 is sent with invalid non-null withdrawals payload and - client is expected to respond 'INVALID' with the appropriate - 'latestValidHash'=='genesis.Hash'. + client is expected to respond with the appropriate error. `, }, WithdrawalsForkHeight: 2, // Genesis and Block 1 are Pre-Withdrawals @@ -76,11 +75,10 @@ var Tests = []test.SpecInterface{ Spec: test.Spec{ Name: "Withdrawals Fork on Block 3", About: ` - Tests the transition to the withdrawals fork after a single block - has happened. + Tests the transition to the withdrawals fork after two blocks + have happened. Block 2 is sent with invalid non-null withdrawals payload and - client is expected to respond 'INVALID' with the appropriate - 'latestValidHash'=='block1.Hash'. + client is expected to respond with the appropriate error. `, }, WithdrawalsForkHeight: 3, // Genesis, Block 1 and 2 are Pre-Withdrawals @@ -209,7 +207,7 @@ var Tests = []test.SpecInterface{ Name: "Sync after 2 blocks - Withdrawals on Genesis - Single Withdrawal Account", About: ` - Spawn a first client, with Withdrawals since genesis - - Withdraw to a single account 16 times each block for 8 blocks + - Withdraw to a single account 16 times each block for 2 blocks - Spawn a secondary client and send FCUV2(head) - Wait for sync and verify withdrawn account's balance `, @@ -224,11 +222,11 @@ var Tests = []test.SpecInterface{ &WithdrawalsSyncSpec{ WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ - Name: "Sync after 2 blocks - Withdrawals on Block 2 - Single Withdrawal Account - No Transactions", + Name: "Sync after 2 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts - No Transactions", About: ` - Spawn a first client - Go through withdrawals fork on Block 2 - - Withdraw to a single account 16 times each block for 8 blocks + - Withdraw to 16 accounts each block for 2 blocks - Spawn a secondary client and send FCUV2(head) - Wait for sync, which include syncing a pre-Withdrawals block, and verify withdrawn account's balance `, @@ -236,7 +234,7 @@ var Tests = []test.SpecInterface{ WithdrawalsForkHeight: 2, WithdrawalsBlockCount: 2, WithdrawalsPerBlock: 16, - WithdrawableAccountCount: 1, + WithdrawableAccountCount: 16, TransactionsPerBlock: common.Big0, }, SyncSteps: 1, @@ -244,11 +242,11 @@ var Tests = []test.SpecInterface{ &WithdrawalsSyncSpec{ WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ Spec: test.Spec{ - Name: "Sync after 2 blocks - Withdrawals on Block 2 - Single Withdrawal Account", + Name: "Sync after 2 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts", About: ` - Spawn a first client - Go through withdrawals fork on Block 2 - - Withdraw to a single account 16 times each block for 8 blocks + - Withdraw to 16 accounts each block for 2 blocks - Spawn a secondary client and send FCUV2(head) - Wait for sync, which include syncing a pre-Withdrawals block, and verify withdrawn account's balance `, @@ -256,7 +254,7 @@ var Tests = []test.SpecInterface{ WithdrawalsForkHeight: 2, WithdrawalsBlockCount: 2, WithdrawalsPerBlock: 16, - WithdrawableAccountCount: 1, + WithdrawableAccountCount: 16, }, SyncSteps: 1, }, @@ -267,7 +265,7 @@ var Tests = []test.SpecInterface{ About: ` - Spawn a first client - Go through withdrawals fork on Block 2 - - Withdraw to a single account 16 times each block for 8 blocks + - Withdraw to many accounts 16 times each block for 128 blocks - Spawn a secondary client and send FCUV2(head) - Wait for sync, which include syncing a pre-Withdrawals block, and verify withdrawn account's balance `, From ca6dfa64d8c6034f75e852ae183021d7478a8de8 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 19 Jan 2023 10:32:20 -0600 Subject: [PATCH 44/49] clients/nethermind: revert docker image to normal --- clients/nethermind/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/nethermind/Dockerfile b/clients/nethermind/Dockerfile index ed891f8533..98551f2617 100644 --- a/clients/nethermind/Dockerfile +++ b/clients/nethermind/Dockerfile @@ -1,5 +1,5 @@ ARG branch=latest -FROM nethermindeth/nethermind:withdrawals_yolo +FROM nethermindeth/hive:$branch RUN apt-get update && apt-get install -y wget RUN wget https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -O /usr/local/bin/jq && \ From 994d92de0dd18adf0cb354c5b79743b1ef7387e5 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 19 Jan 2023 17:08:35 -0600 Subject: [PATCH 45/49] simulators/ethereum/engine: update branch --- go.work.sum | 2 ++ simulators/ethereum/engine/go.mod | 2 +- simulators/ethereum/engine/go.sum | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.work.sum b/go.work.sum index 531125f18e..4e3d136b24 100644 --- a/go.work.sum +++ b/go.work.sum @@ -8,4 +8,6 @@ github.com/c-bata/go-prompt v0.2.2 h1:uyKRz6Z6DUyj49QVijyM339UJV9yhbr70gESwbNU3e github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5 h1:2U0HzY8BJ8hVwDKIzp7y4voR9CX/nvcfymLmg2UiOio= +github.com/lightclient/go-ethereum v1.10.10-0.20230116085521-6ab6d738866f h1:Pox2oxBYKWVEw2JUCyiEybqEIurSkq8VHjRp6s9jdf8= +github.com/lightclient/go-ethereum v1.10.10-0.20230116085521-6ab6d738866f/go.mod h1:n7VlOgCwYheLB/mi+V8ni2yf8K2qM3N9WAmalxkhk+c= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= diff --git a/simulators/ethereum/engine/go.mod b/simulators/ethereum/engine/go.mod index 9fe0831b81..4acc35efb9 100644 --- a/simulators/ethereum/engine/go.mod +++ b/simulators/ethereum/engine/go.mod @@ -72,4 +72,4 @@ require ( gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect ) -replace github.com/ethereum/go-ethereum => github.com/marioevz/go-ethereum v1.10.14-0.20230113164816-9cfc6ec5a607 +replace github.com/ethereum/go-ethereum v1.10.26 => github.com/lightclient/go-ethereum v1.10.10-0.20230116085521-6ab6d738866f diff --git a/simulators/ethereum/engine/go.sum b/simulators/ethereum/engine/go.sum index fdc05ecbe5..8ad2fa4065 100644 --- a/simulators/ethereum/engine/go.sum +++ b/simulators/ethereum/engine/go.sum @@ -299,11 +299,11 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lightclient/go-ethereum v1.10.10-0.20230116085521-6ab6d738866f h1:Pox2oxBYKWVEw2JUCyiEybqEIurSkq8VHjRp6s9jdf8= +github.com/lightclient/go-ethereum v1.10.10-0.20230116085521-6ab6d738866f/go.mod h1:n7VlOgCwYheLB/mi+V8ni2yf8K2qM3N9WAmalxkhk+c= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/marioevz/go-ethereum v1.10.14-0.20230113164816-9cfc6ec5a607 h1:3RAUX8WNcojZXfBGkqmJ0UdjASXl0uz+H5qSGDPNik0= -github.com/marioevz/go-ethereum v1.10.14-0.20230113164816-9cfc6ec5a607/go.mod h1:n7VlOgCwYheLB/mi+V8ni2yf8K2qM3N9WAmalxkhk+c= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= From 09e048b843355945a06f35e8e5b0ebf15e0883a2 Mon Sep 17 00:00:00 2001 From: marioevz Date: Thu, 19 Jan 2023 17:16:48 -0600 Subject: [PATCH 46/49] clients/erigon: use devel --- clients/erigon/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/erigon/Dockerfile b/clients/erigon/Dockerfile index 3876b6c1c0..0d096ea4aa 100644 --- a/clients/erigon/Dockerfile +++ b/clients/erigon/Dockerfile @@ -1,4 +1,4 @@ -ARG branch=docker_withdrawals +ARG branch=devel FROM thorax/erigon:$branch # The upstream erigon container uses a non-root user, but we need From 731037e5dc4c3652c4d640568ae0c9880cd3ff5f Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 20 Jan 2023 13:39:36 -0600 Subject: [PATCH 47/49] simulators/ethereum/engine: Support querying txs from client --- simulators/ethereum/engine/client/engine.go | 1 + simulators/ethereum/engine/client/node/node.go | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/simulators/ethereum/engine/client/engine.go b/simulators/ethereum/engine/client/engine.go index e56de1c2d0..ba3aa9cdcb 100644 --- a/simulators/ethereum/engine/client/engine.go +++ b/simulators/ethereum/engine/client/engine.go @@ -22,6 +22,7 @@ type Eth interface { StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) + TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) } type Engine interface { diff --git a/simulators/ethereum/engine/client/node/node.go b/simulators/ethereum/engine/client/node/node.go index 03f43e7c6e..edf1a519f1 100644 --- a/simulators/ethereum/engine/client/node/node.go +++ b/simulators/ethereum/engine/client/node/node.go @@ -829,6 +829,10 @@ func (n *GethNode) NonceAt(ctx context.Context, account common.Address, blockNum return stateDB.GetNonce(account), nil } +func (n *GethNode) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { + panic("NOT IMPLEMENTED") +} + func (n *GethNode) GetBlockTotalDifficulty(ctx context.Context, hash common.Hash) (*big.Int, error) { block := n.eth.BlockChain().GetBlockByHash(hash) if block == nil { From 11e93854930c9124ed7fb3f7b29ff781581b1884 Mon Sep 17 00:00:00 2001 From: marioevz Date: Fri, 20 Jan 2023 13:40:02 -0600 Subject: [PATCH 48/49] simulators/ethereum/engine: Add eip 3860 invalid tx test --- simulators/ethereum/engine/helper/helper.go | 140 +++++++++------- simulators/ethereum/engine/helper/payload.go | 14 ++ .../ethereum/engine/suites/engine/tests.go | 143 +++++++++++++++-- .../engine/suites/transition/tests.go | 11 +- .../engine/suites/withdrawals/README.md | 8 +- .../engine/suites/withdrawals/tests.go | 150 +++++++++++++++++- 6 files changed, 394 insertions(+), 72 deletions(-) diff --git a/simulators/ethereum/engine/helper/helper.go b/simulators/ethereum/engine/helper/helper.go index 6c417a4756..a3e90fdfcc 100644 --- a/simulators/ethereum/engine/helper/helper.go +++ b/simulators/ethereum/engine/helper/helper.go @@ -2,6 +2,7 @@ package helper import ( "context" + "crypto/ecdsa" "strings" "sync" "time" @@ -337,11 +338,24 @@ const ( DynamicFeeTxOnly ) -func MakeTransaction(nonce uint64, recipient *common.Address, gasLimit uint64, amount *big.Int, payload []byte, txType TestTransactionType) (*types.Transaction, error) { +type TransactionCreator interface { + MakeTransaction(nonce uint64) (*types.Transaction, error) +} + +type BaseTransactionCreator struct { + Recipient *common.Address + GasLimit uint64 + Amount *big.Int + Payload []byte + TxType TestTransactionType + PrivateKey *ecdsa.PrivateKey +} + +func (tc *BaseTransactionCreator) MakeTransaction(nonce uint64) (*types.Transaction, error) { var newTxData types.TxData var txTypeToUse int - switch txType { + switch tc.TxType { case UnspecifiedTransactionType: // Test case has no specific type of transaction to use. // Select the type of tx based on the nonce. @@ -362,73 +376,48 @@ func MakeTransaction(nonce uint64, recipient *common.Address, gasLimit uint64, a case types.LegacyTxType: newTxData = &types.LegacyTx{ Nonce: nonce, - To: recipient, - Value: amount, - Gas: gasLimit, + To: tc.Recipient, + Value: tc.Amount, + Gas: tc.GasLimit, GasPrice: globals.GasPrice, - Data: payload, + Data: tc.Payload, } case types.DynamicFeeTxType: gasFeeCap := new(big.Int).Set(globals.GasPrice) gasTipCap := new(big.Int).Set(globals.GasTipPrice) newTxData = &types.DynamicFeeTx{ Nonce: nonce, - Gas: gasLimit, + Gas: tc.GasLimit, GasTipCap: gasTipCap, GasFeeCap: gasFeeCap, - To: recipient, - Value: amount, - Data: payload, + To: tc.Recipient, + Value: tc.Amount, + Data: tc.Payload, } } tx := types.NewTx(newTxData) - signedTx, err := types.SignTx(tx, types.NewLondonSigner(globals.ChainID), globals.VaultKey) + key := tc.PrivateKey + if key == nil { + key = globals.VaultKey + } + signedTx, err := types.SignTx(tx, types.NewLondonSigner(globals.ChainID), key) if err != nil { return nil, err } return signedTx, nil } -// Determines if the error we got from sending the raw tx is because the client -// already knew the tx (might happen if we produced a re-org where the tx was -// unwind back into the txpool) -func SentTxAlreadyKnown(err error) bool { - return strings.Contains(err.Error(), "already known") -} - -func SendNextTransaction(testCtx context.Context, node client.EngineClient, recipient common.Address, amount *big.Int, payload []byte, txType TestTransactionType) (*types.Transaction, error) { - nonce, err := node.GetNextAccountNonce(testCtx, globals.VaultAccountAddress) - if err != nil { - return nil, err - } - tx, err := MakeTransaction(nonce, &recipient, 75000, amount, payload, txType) - if err != nil { - return nil, err - } - for { - ctx, cancel := context.WithTimeout(testCtx, globals.RPCTimeout) - defer cancel() - err := node.SendTransaction(ctx, tx) - if err == nil { - return tx, nil - } else if SentTxAlreadyKnown(err) { - return tx, nil - } - select { - case <-time.After(time.Second): - case <-testCtx.Done(): - return nil, testCtx.Err() - } - } +// Create a contract filled with zeros without going over the specified GasLimit +type BigContractTransactionCreator struct { + BaseTransactionCreator } -// Method that attempts to create a contract filled with zeros without going over the specified gasLimit -func MakeBigContractTransaction(nonce uint64, gasLimit uint64, txType TestTransactionType) (*types.Transaction, error) { +func (tc *BigContractTransactionCreator) MakeTransaction(nonce uint64) (*types.Transaction, error) { // Total GAS: Gtransaction == 21000, Gcreate == 32000, Gcodedeposit == 200 contractLength := uint64(0) - if gasLimit > (21000 + 32000) { - contractLength = (gasLimit - 21000 - 32000) / 200 + if tc.GasLimit > (21000 + 32000) { + contractLength = (tc.GasLimit - 21000 - 32000) / 200 if contractLength >= 1 { // Reduce by 1 to guarantee using less gas than requested contractLength -= 1 @@ -437,22 +426,65 @@ func MakeBigContractTransaction(nonce uint64, gasLimit uint64, txType TestTransa buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, contractLength) - initCode := []byte{ + tc.Payload = []byte{ 0x67, // PUSH8 } - initCode = append(initCode, buf...) // Size of the contract in byte length - initCode = append(initCode, 0x38) // CODESIZE == 0x00 - initCode = append(initCode, 0xF3) // RETURN(offset, length) + tc.Payload = append(tc.Payload, buf...) // Size of the contract in byte length + tc.Payload = append(tc.Payload, 0x38) // CODESIZE == 0x00 + tc.Payload = append(tc.Payload, 0xF3) // RETURN(offset, length) + if tc.Recipient != nil { + panic("invalid configuration for big contract tx creator") + } + return tc.BaseTransactionCreator.MakeTransaction(nonce) +} - return MakeTransaction(nonce, nil, gasLimit, common.Big0, initCode, txType) +// Create a tx with the specified initcode length (all zeros) +type BigInitcodeTransactionCreator struct { + BaseTransactionCreator + InitcodeLength int + PadByte uint8 + Initcode []byte } -func SendNextBigContractTransaction(testCtx context.Context, node client.EngineClient, gasLimit uint64, txType TestTransactionType) (*types.Transaction, error) { +func (tc *BigInitcodeTransactionCreator) MakeTransaction(nonce uint64) (*types.Transaction, error) { + // This method caches the payload with the crafted initcode after first execution. + if tc.Payload == nil { + // Prepare initcode payload + if tc.Initcode != nil { + if len(tc.Initcode) > tc.InitcodeLength { + panic(fmt.Errorf("invalid initcode (too big)")) + } + tc.Payload = tc.Initcode + } else { + tc.Payload = []byte{} + } + + for { + if len(tc.Payload) == tc.InitcodeLength { + break + } + tc.Payload = append(tc.Payload, tc.PadByte) + } + } + if tc.Recipient != nil { + panic("invalid configuration for big contract tx creator") + } + return tc.BaseTransactionCreator.MakeTransaction(nonce) +} + +// Determines if the error we got from sending the raw tx is because the client +// already knew the tx (might happen if we produced a re-org where the tx was +// unwind back into the txpool) +func SentTxAlreadyKnown(err error) bool { + return strings.Contains(err.Error(), "already known") +} + +func SendNextTransaction(testCtx context.Context, node client.EngineClient, txCreator TransactionCreator) (*types.Transaction, error) { nonce, err := node.GetNextAccountNonce(testCtx, globals.VaultAccountAddress) if err != nil { return nil, err } - tx, err := MakeBigContractTransaction(nonce, gasLimit, txType) + tx, err := txCreator.MakeTransaction(nonce) if err != nil { return nil, err } @@ -462,6 +494,8 @@ func SendNextBigContractTransaction(testCtx context.Context, node client.EngineC err := node.SendTransaction(ctx, tx) if err == nil { return tx, nil + } else if SentTxAlreadyKnown(err) { + return tx, nil } select { case <-time.After(time.Second): diff --git a/simulators/ethereum/engine/helper/payload.go b/simulators/ethereum/engine/helper/payload.go index 7ee1e1dd5c..0a9a86b22f 100644 --- a/simulators/ethereum/engine/helper/payload.go +++ b/simulators/ethereum/engine/helper/payload.go @@ -133,6 +133,20 @@ func CustomizePayload(basePayload *api.ExecutableData, customData *CustomPayload return result, nil } +func CustomizePayloadTransactions(basePayload *api.ExecutableData, customTransactions types.Transactions) (*api.ExecutableData, error) { + byteTxs := make([][]byte, 0) + for _, tx := range customTransactions { + bytes, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + byteTxs = append(byteTxs, bytes) + } + return CustomizePayload(basePayload, &CustomPayloadData{ + Transactions: &byteTxs, + }) +} + func (customData *CustomPayloadData) String() string { customFieldsList := make([]string, 0) if customData.ParentHash != nil { diff --git a/simulators/ethereum/engine/suites/engine/tests.go b/simulators/ethereum/engine/suites/engine/tests.go index b123a1c097..26e78786f6 100644 --- a/simulators/ethereum/engine/suites/engine/tests.go +++ b/simulators/ethereum/engine/suites/engine/tests.go @@ -30,6 +30,7 @@ var ( Pending = big.NewInt(-2) Finalized = big.NewInt(-3) Safe = big.NewInt(-4) + ZeroAddr = common.Address{} ) var Tests = []test.Spec{ @@ -1653,7 +1654,16 @@ func invalidTransitionPayload(t *test.Env) { // Produce two blocks before trying to re-org t.CLMock.ProduceBlocks(2, clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { - _, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, big1, nil, t.TestTransactionType) + _, err := helper.SendNextTransaction( + t.TestContext, + t.CLMock.NextBlockProducer, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: big1, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -1711,7 +1721,16 @@ func invalidPayloadTestCaseGen(payloadField helper.InvalidPayloadBlockField, syn if !emptyTxs { // Function to send at least one transaction each block produced // Send the transaction to the globals.PrevRandaoContractAddr - _, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, big1, nil, t.TestTransactionType) + _, err := helper.SendNextTransaction( + t.TestContext, + t.CLMock.NextBlockProducer, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: big1, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -1934,7 +1953,16 @@ func invalidMissingAncestorReOrgGen(invalid_index int, payloadField helper.Inval // Empty Txs Payload with invalid stateRoot discovered an issue in geth sync, hence this is customizable. if !emptyTxs { // Send the transaction to the globals.PrevRandaoContractAddr - _, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, big1, nil, t.TestTransactionType) + _, err := helper.SendNextTransaction( + t.TestContext, + t.CLMock.NextBlockProducer, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: big1, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -2136,7 +2164,16 @@ func (spec InvalidMissingAncestorReOrgSpec) GenerateSync() func(*test.Env) { // Empty Txs Payload with invalid stateRoot discovered an issue in geth sync, hence this is customizable. if !spec.EmptyTransactions { // Send the transaction to the globals.PrevRandaoContractAddr - _, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, big1, nil, t.TestTransactionType) + _, err := helper.SendNextTransaction( + t.TestContext, + t.CLMock.NextBlockProducer, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: big1, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -2376,7 +2413,15 @@ func blockStatusExecPayloadGen(transitionBlock bool) func(t *test.Env) { t.CLMock.ProduceSingleBlock(clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { var err error - tx, err = helper.SendNextTransaction(t.TestContext, t.Engine, (common.Address{}), big1, nil, t.TestTransactionType) + tx, err = helper.SendNextTransaction( + t.TestContext, + t.Engine, &helper.BaseTransactionCreator{ + Recipient: &ZeroAddr, + Amount: big1, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", err) } @@ -2415,7 +2460,16 @@ func blockStatusHeadBlockGen(transitionBlock bool) func(t *test.Env) { t.CLMock.ProduceSingleBlock(clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { var err error - tx, err = helper.SendNextTransaction(t.TestContext, t.Engine, (common.Address{}), big1, nil, t.TestTransactionType) + tx, err = helper.SendNextTransaction( + t.TestContext, + t.Engine, + &helper.BaseTransactionCreator{ + Recipient: &ZeroAddr, + Amount: big1, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -2815,7 +2869,16 @@ func transactionReorg(t *test.Env) { data := common.LeftPadBytes([]byte{byte(i)}, 32) t.Logf("transactionReorg, i=%v, data=%v\n", i, data) var err error - tx, err = helper.SendNextTransaction(t.TestContext, t.Engine, sstoreContractAddr, big0, data, t.TestTransactionType) + tx, err = helper.SendNextTransaction( + t.TestContext, + t.Engine, + &helper.BaseTransactionCreator{ + Recipient: &sstoreContractAddr, + Amount: big0, + Payload: data, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -2914,7 +2977,16 @@ func transactionReorgBlockhash(newNPOnRevert bool) func(t *test.Env) { data := common.LeftPadBytes([]byte{byte(i)}, 32) t.Logf("transactionReorg, i=%v, data=%v\n", i, data) var err error - tx, err = helper.SendNextTransaction(t.TestContext, t.Engine, sstoreContractAddr, big0, data, t.TestTransactionType) + tx, err = helper.SendNextTransaction( + t.TestContext, + t.Engine, + &helper.BaseTransactionCreator{ + Recipient: &sstoreContractAddr, + Amount: big0, + Payload: data, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -3010,7 +3082,16 @@ func sidechainReorg(t *test.Env) { // Produce two payloads, send fcU with first payload, check transaction outcome, then reorg, check transaction outcome again // This single transaction will change its outcome based on the payload - tx, err := helper.SendNextTransaction(t.TestContext, t.Engine, globals.PrevRandaoContractAddr, big0, nil, t.TestTransactionType) + tx, err := helper.SendNextTransaction( + t.TestContext, + t.Engine, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: big0, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -3148,7 +3229,16 @@ func inOrderPayloads(t *test.Env) { // We send the transactions after we got the Payload ID, before the CLMocker gets the prepared Payload OnPayloadProducerSelected: func() { for i := 0; i < txPerPayload; i++ { - _, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, recipient, amountPerTx, nil, t.TestTransactionType) + _, err := helper.SendNextTransaction( + t.TestContext, + t.CLMock.NextBlockProducer, + &helper.BaseTransactionCreator{ + Recipient: &recipient, + Amount: amountPerTx, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -3471,7 +3561,16 @@ func suggestedFeeRecipient(t *test.Env) { // Send multiple transactions for i := 0; i < txCount; i++ { - _, err := helper.SendNextTransaction(t.TestContext, t.Engine, globals.VaultAccountAddress, big0, nil, t.TestTransactionType) + _, err := helper.SendNextTransaction( + t.TestContext, + t.Engine, + &helper.BaseTransactionCreator{ + Recipient: &globals.VaultAccountAddress, + Amount: big0, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -3531,7 +3630,16 @@ func prevRandaoOpcodeTx(t *test.Env) { // Try to send many transactions before PoW transition to guarantee at least one enters in the block go func(t *test.Env) { for { - _, err := helper.SendNextTransaction(t.TestContext, t.Engine, globals.PrevRandaoContractAddr, big0, nil, t.TestTransactionType) + _, err := helper.SendNextTransaction( + t.TestContext, + t.Engine, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: big0, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -3574,7 +3682,16 @@ func prevRandaoOpcodeTx(t *test.Env) { ) t.CLMock.ProduceBlocks(txCount, clmock.BlockProcessCallbacks{ OnPayloadProducerSelected: func() { - tx, err := helper.SendNextTransaction(t.TestContext, t.Engine, globals.PrevRandaoContractAddr, big0, nil, t.TestTransactionType) + tx, err := helper.SendNextTransaction( + t.TestContext, + t.Engine, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: big0, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } diff --git a/simulators/ethereum/engine/suites/transition/tests.go b/simulators/ethereum/engine/suites/transition/tests.go index daa1854305..b60037d0d0 100644 --- a/simulators/ethereum/engine/suites/transition/tests.go +++ b/simulators/ethereum/engine/suites/transition/tests.go @@ -1025,7 +1025,16 @@ func GenerateMergeTestSpec(mergeTestSpec MergeTestSpec) test.Spec { // Get the address nonce: // This is because we could have included transactions in the PoW chain of the block // producer, or re-orged. - tx, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, common.Big0, nil, t.TestTransactionType) + tx, err := helper.SendNextTransaction( + t.TestContext, + t.CLMock.NextBlockProducer, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: common.Big0, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Unable create next transaction: %v", t.TestName, err) } diff --git a/simulators/ethereum/engine/suites/withdrawals/README.md b/simulators/ethereum/engine/suites/withdrawals/README.md index 8adf841976..cd25ceca14 100644 --- a/simulators/ethereum/engine/suites/withdrawals/README.md +++ b/simulators/ethereum/engine/suites/withdrawals/README.md @@ -144,9 +144,11 @@ This test suite verifies behavior of the Engine API on each client after the Sha - Verify client `A` syncs side chain blocks from client `B` re-org was correctly applied along with the withdrawal balances. ## Max Initcode Tests -- [NOT IMPLEMENTED] Transactions exceeding max initcode should be immediately rejected: +- Transactions exceeding max initcode should be immediately rejected: - Send two transactions with valid nonces each - `TxA`, a smart contract creating transaction with an initcode length equal to MAX_INITCODE_SIZE ([EIP-3860](https://eips.ethereum.org/EIPS/eip-3860)) - `TxB`, a smart contract creating transaction with an initcode length equal to MAX_INITCODE_SIZE+1. - - Verify that `TxB` returns error on `eth_sendRawTransaction` and also should be absend from the transaction pool of the client - - Request a new payload from the client and verify that the payload built only includes `TxA`, and `TxB` is not included, nor the contract it could create is present in the `stateRoot`. \ No newline at end of file + - Verify that `TxB` returns error on `eth_sendRawTransaction` and also should be absent from the transaction pool of the client + - Request a new payload from the client and verify that the payload built only includes `TxA`, and `TxB` is not included, nor the contract it could create is present in the `stateRoot`. + - Create a modified payload where `TxA` is replaced by `TxB` and send using `engine_newPayloadV2` + - Verify that `engine_newPayloadV2` returns `INVALID` nad `latestValidHash` points to the latest valid payload in the canonical chain. \ No newline at end of file diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 31e6a4cd5b..6450ced3ef 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -2,6 +2,7 @@ package suite_withdrawals import ( + "bytes" "encoding/json" "fmt" "math/big" @@ -24,6 +25,7 @@ var ( Finalized = big.NewInt(-3) Safe = big.NewInt(-4) InvalidParamsError = -32602 + MAX_INITCODE_SIZE = 49152 ) // Execution specification reference: @@ -454,6 +456,19 @@ var Tests = []test.SpecInterface{ SidechainTimeIncrements: 1, }, // TODO: REORG SYNC WHERE SYNCED BLOCKS HAVE WITHDRAWALS BEFORE TIME + + // EVM Tests (EIP-3651, EIP-3855, EIP-3860) + &MaxInitcodeSizeSpec{ + WithdrawalsBaseSpec: &WithdrawalsBaseSpec{ + Spec: test.Spec{ + Name: "Max Initcode Size", + }, + WithdrawalsForkHeight: 2, // Block 1 is Pre-Withdrawals + WithdrawalsBlockCount: 2, + }, + OverflowMaxInitcodeTxCountBeforeFork: 0, + OverflowMaxInitcodeTxCountAfterFork: 1, + }, } // Helper types to convert gwei into wei more easily @@ -773,7 +788,16 @@ func (ws *WithdrawalsBaseSpec) Execute(t *test.Env) { ws.WithdrawalsHistory[t.CLMock.CurrentPayloadNumber] = t.CLMock.NextWithdrawals // Send some transactions for i := uint64(0); i < ws.GetTransactionCountPerPayload(); i++ { - _, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, common.Big1, nil, t.TestTransactionType) + _, err := helper.SendNextTransaction( + t.TestContext, + t.CLMock.NextBlockProducer, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: common.Big1, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -1028,7 +1052,16 @@ func (ws *WithdrawalsReorgSpec) Execute(t *test.Env) { OnRequestNextPayload: func() { // Send transactions to be included in the payload for i := uint64(0); i < ws.GetTransactionCountPerPayload(); i++ { - tx, err := helper.SendNextTransaction(t.TestContext, t.CLMock.NextBlockProducer, globals.PrevRandaoContractAddr, common.Big1, nil, t.TestTransactionType) + tx, err := helper.SendNextTransaction( + t.TestContext, + t.CLMock.NextBlockProducer, + &helper.BaseTransactionCreator{ + Recipient: &globals.PrevRandaoContractAddr, + Amount: common.Big1, + Payload: nil, + TxType: t.TestTransactionType, + }, + ) if err != nil { t.Fatalf("FAIL (%s): Error trying to send transaction: %v", t.TestName, err) } @@ -1235,3 +1268,116 @@ func (ws *WithdrawalsReorgSpec) Execute(t *test.Env) { }, nil) r.ExpectPayloadStatus(test.Valid) } + +// EIP-3860 Shanghai Tests: +// Send transactions overflowing the MAX_INITCODE_SIZE +// limit set in EIP-3860, before and after the Shanghai +// fork. +type MaxInitcodeSizeSpec struct { + *WithdrawalsBaseSpec + OverflowMaxInitcodeTxCountBeforeFork uint64 + OverflowMaxInitcodeTxCountAfterFork uint64 +} + +func (s *MaxInitcodeSizeSpec) Execute(t *test.Env) { + t.CLMock.WaitForTTD() + + invalidTxCreator := &helper.BigInitcodeTransactionCreator{ + InitcodeLength: MAX_INITCODE_SIZE + 1, + BaseTransactionCreator: helper.BaseTransactionCreator{ + GasLimit: 2000000, + }, + } + validTxCreator := &helper.BigInitcodeTransactionCreator{ + InitcodeLength: MAX_INITCODE_SIZE, + BaseTransactionCreator: helper.BaseTransactionCreator{ + GasLimit: 2000000, + }, + } + + if s.OverflowMaxInitcodeTxCountBeforeFork > 0 { + if s.GetPreWithdrawalsBlockCount() == 0 { + panic("invalid test configuration") + } + + for i := uint64(0); i < s.OverflowMaxInitcodeTxCountBeforeFork; i++ { + tx, err := invalidTxCreator.MakeTransaction(i) + if err != nil { + t.Fatalf("FAIL: Error creating max initcode transaction: %v", err) + } + err = t.Engine.SendTransaction(t.TestContext, tx) + if err != nil { + t.Fatalf("FAIL: Error sending max initcode transaction before Shanghai: %v", err) + } + } + } + + // Produce all blocks needed to reach Shanghai + t.Logf("INFO: Blocks until Shanghai=%d", s.GetPreWithdrawalsBlockCount()) + txIncluded := uint64(0) + t.CLMock.ProduceBlocks(int(s.GetPreWithdrawalsBlockCount()), clmock.BlockProcessCallbacks{ + OnGetPayload: func() { + t.Logf("INFO: Got Pre-Shanghai block=%d", t.CLMock.LatestPayloadBuilt.Number) + txIncluded += uint64(len(t.CLMock.LatestPayloadBuilt.Transactions)) + }, + }) + + // Check how many transactions were included + if txIncluded == 0 && s.OverflowMaxInitcodeTxCountBeforeFork > 0 { + t.Fatalf("FAIL: No max initcode txs included before Shanghai. Txs must have been included before the MAX_INITCODE_SIZE limit was enabled") + } + + // Create a payload, no txs should be included + t.CLMock.ProduceSingleBlock(clmock.BlockProcessCallbacks{ + OnGetPayload: func() { + if len(t.CLMock.LatestPayloadBuilt.Transactions) > 0 { + t.Fatalf("FAIL: Client included tx exceeding the MAX_INITCODE_SIZE in payload") + } + }, + }) + + // Send transactions after the fork + for i := txIncluded; i < (txIncluded + s.OverflowMaxInitcodeTxCountAfterFork); i++ { + tx, err := invalidTxCreator.MakeTransaction(i) + if err != nil { + t.Fatalf("FAIL: Error creating max initcode transaction: %v", err) + } + err = t.Engine.SendTransaction(t.TestContext, tx) + if err == nil { + t.Fatalf("FAIL: Client accepted tx exceeding the MAX_INITCODE_SIZE: %v", tx) + } + txBack, isPending, err := t.Engine.TransactionByHash(t.TestContext, tx.Hash()) + if txBack != nil || isPending || err == nil { + t.Fatalf("FAIL: Invalid tx was not unknown to the client: txBack=%v, isPending=%t, err=%v", txBack, isPending, err) + } + } + + // Try to include an invalid tx in new payload + var ( + validTx, _ = validTxCreator.MakeTransaction(txIncluded) + invalidTx, _ = invalidTxCreator.MakeTransaction(txIncluded) + ) + t.CLMock.ProduceSingleBlock(clmock.BlockProcessCallbacks{ + OnPayloadProducerSelected: func() { + t.Engine.SendTransaction(t.TestContext, validTx) + }, + OnGetPayload: func() { + validTxBytes, err := validTx.MarshalBinary() + if err != nil { + t.Fatalf("FAIL: Unable to marshal valid tx to binary: %v", err) + } + if len(t.CLMock.LatestPayloadBuilt.Transactions) != 1 || !bytes.Equal(validTxBytes, t.CLMock.LatestPayloadBuilt.Transactions[0]) { + t.Fatalf("FAIL: Client did not include valid tx with MAX_INITCODE_SIZE") + } + // Customize the payload to include a tx with an invalid initcode + customPayload, err := helper.CustomizePayloadTransactions(&t.CLMock.LatestPayloadBuilt, types.Transactions{invalidTx}) + if err != nil { + t.Fatalf("FAIL: Unable to customize payload: %v", err) + } + + r := t.TestEngine.TestEngineNewPayloadV2(customPayload) + r.ExpectStatus(test.Invalid) + r.ExpectLatestValidHash(&t.CLMock.LatestPayloadBuilt.ParentHash) + }, + }) +} From 835ce3d1f5bb4577e4e7ecc752813cf5c6da1de8 Mon Sep 17 00:00:00 2001 From: marioevz Date: Mon, 23 Jan 2023 08:07:48 -0600 Subject: [PATCH 49/49] simulators/ethereum/engine: fix gaslimit everywhere --- simulators/ethereum/engine/suites/engine/tests.go | 13 +++++++++++++ .../ethereum/engine/suites/transition/tests.go | 1 + .../ethereum/engine/suites/withdrawals/tests.go | 2 ++ 3 files changed, 16 insertions(+) diff --git a/simulators/ethereum/engine/suites/engine/tests.go b/simulators/ethereum/engine/suites/engine/tests.go index 26e78786f6..c6716c428c 100644 --- a/simulators/ethereum/engine/suites/engine/tests.go +++ b/simulators/ethereum/engine/suites/engine/tests.go @@ -1662,6 +1662,7 @@ func invalidTransitionPayload(t *test.Env) { Amount: big1, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -1729,6 +1730,7 @@ func invalidPayloadTestCaseGen(payloadField helper.InvalidPayloadBlockField, syn Amount: big1, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -1961,6 +1963,7 @@ func invalidMissingAncestorReOrgGen(invalid_index int, payloadField helper.Inval Amount: big1, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -2172,6 +2175,7 @@ func (spec InvalidMissingAncestorReOrgSpec) GenerateSync() func(*test.Env) { Amount: big1, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -2420,6 +2424,7 @@ func blockStatusExecPayloadGen(transitionBlock bool) func(t *test.Env) { Amount: big1, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -2468,6 +2473,7 @@ func blockStatusHeadBlockGen(transitionBlock bool) func(t *test.Env) { Amount: big1, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -2877,6 +2883,7 @@ func transactionReorg(t *test.Env) { Amount: big0, Payload: data, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -2985,6 +2992,7 @@ func transactionReorgBlockhash(newNPOnRevert bool) func(t *test.Env) { Amount: big0, Payload: data, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -3090,6 +3098,7 @@ func sidechainReorg(t *test.Env) { Amount: big0, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -3237,6 +3246,7 @@ func inOrderPayloads(t *test.Env) { Amount: amountPerTx, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -3569,6 +3579,7 @@ func suggestedFeeRecipient(t *test.Env) { Amount: big0, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -3638,6 +3649,7 @@ func prevRandaoOpcodeTx(t *test.Env) { Amount: big0, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -3690,6 +3702,7 @@ func prevRandaoOpcodeTx(t *test.Env) { Amount: big0, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { diff --git a/simulators/ethereum/engine/suites/transition/tests.go b/simulators/ethereum/engine/suites/transition/tests.go index b60037d0d0..2655d1c507 100644 --- a/simulators/ethereum/engine/suites/transition/tests.go +++ b/simulators/ethereum/engine/suites/transition/tests.go @@ -1033,6 +1033,7 @@ func GenerateMergeTestSpec(mergeTestSpec MergeTestSpec) test.Spec { Amount: common.Big0, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { diff --git a/simulators/ethereum/engine/suites/withdrawals/tests.go b/simulators/ethereum/engine/suites/withdrawals/tests.go index 6450ced3ef..cbf81005e3 100644 --- a/simulators/ethereum/engine/suites/withdrawals/tests.go +++ b/simulators/ethereum/engine/suites/withdrawals/tests.go @@ -796,6 +796,7 @@ func (ws *WithdrawalsBaseSpec) Execute(t *test.Env) { Amount: common.Big1, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil { @@ -1060,6 +1061,7 @@ func (ws *WithdrawalsReorgSpec) Execute(t *test.Env) { Amount: common.Big1, Payload: nil, TxType: t.TestTransactionType, + GasLimit: 75000, }, ) if err != nil {