From eaf0348bd07b61cbb9f68375c3d83bda800a44a9 Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Mon, 25 Dec 2023 02:34:13 +0100 Subject: [PATCH] [Grindmas] Added tests to Beacon API, also fixed stuff. (#9074) * Testing Beacon API * Fixed sentinel code (a little bit) * Fixed sentinel tests * Added historical state support * Fixed state-related endpoints (i was drunk when writing them) --- cl/antiquary/antiquary.go | 7 +- cl/antiquary/state_antiquary.go | 22 +- cl/antiquary/state_antiquary_test.go | 4 +- cl/antiquary/tests/tests.go | 26 +- cl/beacon/beaconhttp/api.go | 7 +- cl/beacon/handler/blocks.go | 4 +- cl/beacon/handler/blocks_test.go | 257 ++++++++++ cl/beacon/handler/config_test.go | 81 ++++ cl/beacon/handler/duties_proposer.go | 36 +- cl/beacon/handler/duties_proposer_test.go | 112 +++++ cl/beacon/handler/format.go | 9 + cl/beacon/handler/genesis.go | 2 +- cl/beacon/handler/genesis_test.go | 35 ++ cl/beacon/handler/handler.go | 12 +- cl/beacon/handler/headers_test.go | 178 +++++++ cl/beacon/handler/states.go | 91 ++-- cl/beacon/handler/states_test.go | 452 ++++++++++++++++++ cl/beacon/handler/utils_test.go | 64 +++ cl/beacon/handler/validators.go | 128 +++++ cl/beacon/synced_data/synced_data.go | 21 +- .../historical_states_reader.go | 8 +- .../historical_states_reader_test.go | 4 +- cl/persistence/state/state_accessors.go | 3 + cl/phase1/forkchoice/forkchoice_mock.go | 174 +++++++ cl/sentinel/handlers/handlers.go | 5 +- cl/sentinel/sentinel_gossip_test.go | 76 +-- cl/sentinel/sentinel_requests_test.go | 4 +- cmd/capcli/cli.go | 16 +- cmd/caplin/caplin1/run.go | 2 +- 29 files changed, 1666 insertions(+), 174 deletions(-) create mode 100644 cl/beacon/handler/blocks_test.go create mode 100644 cl/beacon/handler/config_test.go create mode 100644 cl/beacon/handler/duties_proposer_test.go create mode 100644 cl/beacon/handler/genesis_test.go create mode 100644 cl/beacon/handler/headers_test.go create mode 100644 cl/beacon/handler/states_test.go create mode 100644 cl/beacon/handler/utils_test.go create mode 100644 cl/beacon/handler/validators.go create mode 100644 cl/phase1/forkchoice/forkchoice_mock.go diff --git a/cl/antiquary/antiquary.go b/cl/antiquary/antiquary.go index 27273ed5d5b..1271bb2fec4 100644 --- a/cl/antiquary/antiquary.go +++ b/cl/antiquary/antiquary.go @@ -34,7 +34,7 @@ type Antiquary struct { beaconDB persistence.BlockSource backfilled *atomic.Bool cfg *clparams.BeaconChainConfig - states bool + states, blocks bool fs afero.Fs validatorsTable *state_accessors.StaticValidatorTable genesisState *state.CachingBeaconState @@ -43,7 +43,7 @@ type Antiquary struct { balances32 []byte } -func NewAntiquary(ctx context.Context, genesisState *state.CachingBeaconState, validatorsTable *state_accessors.StaticValidatorTable, cfg *clparams.BeaconChainConfig, dirs datadir.Dirs, downloader proto_downloader.DownloaderClient, mainDB kv.RwDB, sn *freezeblocks.CaplinSnapshots, reader freezeblocks.BeaconSnapshotReader, beaconDB persistence.BlockSource, logger log.Logger, states bool, fs afero.Fs) *Antiquary { +func NewAntiquary(ctx context.Context, genesisState *state.CachingBeaconState, validatorsTable *state_accessors.StaticValidatorTable, cfg *clparams.BeaconChainConfig, dirs datadir.Dirs, downloader proto_downloader.DownloaderClient, mainDB kv.RwDB, sn *freezeblocks.CaplinSnapshots, reader freezeblocks.BeaconSnapshotReader, beaconDB persistence.BlockSource, logger log.Logger, states, blocks bool, fs afero.Fs) *Antiquary { backfilled := &atomic.Bool{} backfilled.Store(false) return &Antiquary{ @@ -61,12 +61,13 @@ func NewAntiquary(ctx context.Context, genesisState *state.CachingBeaconState, v fs: fs, validatorsTable: validatorsTable, genesisState: genesisState, + blocks: blocks, } } // Antiquate is the function that starts transactions seeding and shit, very cool but very shit too as a name. func (a *Antiquary) Loop() error { - if a.downloader == nil { + if a.downloader == nil || !a.blocks { return nil // Just skip if we don't have a downloader } // Skip if we dont support backfilling for the current network diff --git a/cl/antiquary/state_antiquary.go b/cl/antiquary/state_antiquary.go index fb12ea39440..61c2c057b51 100644 --- a/cl/antiquary/state_antiquary.go +++ b/cl/antiquary/state_antiquary.go @@ -231,7 +231,9 @@ func (s *Antiquary) IncrementBeaconState(ctx context.Context, to uint64) error { return err } log.Info("Recovered Beacon State", "slot", s.currentState.Slot(), "elapsed", end, "root", libcommon.Hash(hashRoot).String()) - + if err := s.currentState.InitBeaconState(); err != nil { + return err + } } s.balances32 = s.balances32[:0] s.balances32 = append(s.balances32, s.currentState.RawBalances()...) @@ -734,16 +736,16 @@ func (s *Antiquary) collectGenesisState(ctx context.Context, compressor *zstd.En if err := s.antiquateFullUint64List(inactivities, slot, state.RawInactivityScores(), &commonBuffer, compressor); err != nil { return err } - } - - committee := *state.CurrentSyncCommittee() - if err := currentSyncCommittee.Collect(base_encoding.Encode64ToBytes4(slot), libcommon.Copy(committee[:])); err != nil { - return err - } + committeeSlot := s.cfg.RoundSlotToSyncCommitteePeriod(slot) + committee := *state.CurrentSyncCommittee() + if err := currentSyncCommittee.Collect(base_encoding.Encode64ToBytes4(committeeSlot), libcommon.Copy(committee[:])); err != nil { + return err + } - committee = *state.NextSyncCommittee() - if err := nextSyncCommittee.Collect(base_encoding.Encode64ToBytes4(slot), libcommon.Copy(committee[:])); err != nil { - return err + committee = *state.NextSyncCommittee() + if err := nextSyncCommittee.Collect(base_encoding.Encode64ToBytes4(committeeSlot), libcommon.Copy(committee[:])); err != nil { + return err + } } var b bytes.Buffer diff --git a/cl/antiquary/state_antiquary_test.go b/cl/antiquary/state_antiquary_test.go index 352deae3fd7..3f198e7fe44 100644 --- a/cl/antiquary/state_antiquary_test.go +++ b/cl/antiquary/state_antiquary_test.go @@ -19,12 +19,12 @@ import ( func runTest(t *testing.T, blocks []*cltypes.SignedBeaconBlock, preState, postState *state.CachingBeaconState) { db := memdb.NewTestDB(t) - reader, _ := tests.LoadChain(blocks, db, t) + reader, _ := tests.LoadChain(blocks, postState, db, t) ctx := context.Background() vt := state_accessors.NewStaticValidatorTable() f := afero.NewMemMapFs() - a := NewAntiquary(ctx, preState, vt, &clparams.MainnetBeaconConfig, datadir.New("/tmp"), nil, db, nil, reader, nil, log.New(), true, f) + a := NewAntiquary(ctx, preState, vt, &clparams.MainnetBeaconConfig, datadir.New("/tmp"), nil, db, nil, reader, nil, log.New(), true, true, f) require.NoError(t, a.IncrementBeaconState(ctx, blocks[len(blocks)-1].Block.Slot+33)) // TODO: add more meaning here, like checking db values, will do so once i see some bugs } diff --git a/cl/antiquary/tests/tests.go b/cl/antiquary/tests/tests.go index 47bb2848dc6..ddfb042405d 100644 --- a/cl/antiquary/tests/tests.go +++ b/cl/antiquary/tests/tests.go @@ -13,6 +13,7 @@ import ( "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/erigon/cl/persistence" "github.com/ledgerwatch/erigon/cl/persistence/beacon_indicies" + state_accessors "github.com/ledgerwatch/erigon/cl/persistence/state" "github.com/ledgerwatch/erigon/cl/phase1/core/state" "github.com/ledgerwatch/erigon/cl/utils" "github.com/spf13/afero" @@ -61,17 +62,35 @@ func (m *MockBlockReader) ReadBlockBySlot(ctx context.Context, tx kv.Tx, slot ui } func (m *MockBlockReader) ReadBlockByRoot(ctx context.Context, tx kv.Tx, blockRoot libcommon.Hash) (*cltypes.SignedBeaconBlock, error) { - panic("implement me") + // do a linear search + for _, v := range m.u { + r, err := v.Block.HashSSZ() + if err != nil { + return nil, err + } + + if r == blockRoot { + return v, nil + } + } + return nil, nil } func (m *MockBlockReader) ReadHeaderByRoot(ctx context.Context, tx kv.Tx, blockRoot libcommon.Hash) (*cltypes.SignedBeaconBlockHeader, error) { - panic("implement me") + block, err := m.ReadBlockByRoot(ctx, tx, blockRoot) + if err != nil { + return nil, err + } + if block == nil { + return nil, nil + } + return block.SignedBeaconBlockHeader(), nil } func (m *MockBlockReader) FrozenSlots() uint64 { panic("implement me") } -func LoadChain(blocks []*cltypes.SignedBeaconBlock, db kv.RwDB, t *testing.T) (*MockBlockReader, afero.Fs) { +func LoadChain(blocks []*cltypes.SignedBeaconBlock, s *state.CachingBeaconState, db kv.RwDB, t *testing.T) (*MockBlockReader, afero.Fs) { tx, err := db.BeginRw(context.Background()) require.NoError(t, err) defer tx.Rollback() @@ -86,6 +105,7 @@ func LoadChain(blocks []*cltypes.SignedBeaconBlock, db kv.RwDB, t *testing.T) (* require.NoError(t, source.WriteBlock(context.Background(), tx, block, true)) require.NoError(t, beacon_indicies.WriteHighestFinalized(tx, block.Block.Slot+64)) } + require.NoError(t, state_accessors.InitializeStaticTables(tx, s)) require.NoError(t, tx.Commit()) return m, fs diff --git a/cl/beacon/beaconhttp/api.go b/cl/beacon/beaconhttp/api.go index 7c649d579dd..46172ecb0d6 100644 --- a/cl/beacon/beaconhttp/api.go +++ b/cl/beacon/beaconhttp/api.go @@ -71,7 +71,12 @@ func HandleEndpoint[T any](h EndpointHandler[T]) http.HandlerFunc { ans, err := h.Handle(r) if err != nil { log.Error("beacon api request error", "err", err) - endpointError := WrapEndpointError(err) + var endpointError *EndpointError + if e, ok := err.(*EndpointError); ok { + endpointError = e + } else { + endpointError = WrapEndpointError(err) + } endpointError.WriteTo(w) return } diff --git a/cl/beacon/handler/blocks.go b/cl/beacon/handler/blocks.go index 8f0a274e43e..6e608ef03d8 100644 --- a/cl/beacon/handler/blocks.go +++ b/cl/beacon/handler/blocks.go @@ -193,5 +193,7 @@ func (a *ApiHandler) getBlockRoot(r *http.Request) (*beaconResponse, error) { if err != nil { return nil, err } - return newBeaconResponse(struct{ Root libcommon.Hash }{Root: root}).withFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()), nil + return newBeaconResponse(struct { + Root libcommon.Hash `json:"root"` + }{Root: root}).withFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()), nil } diff --git a/cl/beacon/handler/blocks_test.go b/cl/beacon/handler/blocks_test.go new file mode 100644 index 00000000000..011e0d32049 --- /dev/null +++ b/cl/beacon/handler/blocks_test.go @@ -0,0 +1,257 @@ +package handler + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/common" + "github.com/stretchr/testify/require" +) + +func TestGetBlindedBlock(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + // Start by testing + rootBlock1, err := blocks[0].Block.HashSSZ() + if err != nil { + t.Fatal(err) + } + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + + cases := []struct { + blockID string + code int + slot uint64 + }{ + { + blockID: "0x" + common.Bytes2Hex(rootBlock1[:]), + code: http.StatusOK, + slot: blocks[0].Block.Slot, + }, + { + blockID: "head", + code: http.StatusOK, + slot: blocks[len(blocks)-1].Block.Slot, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/blocks/{block_id} + resp, err := http.Get(server.URL + "/eth/v1/beacon/blinded_blocks/" + c.blockID) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + jsonVal := make(map[string]interface{}) + // unmarshal the json + require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal)) + data := jsonVal["data"].(map[string]interface{}) + message := data["message"].(map[string]interface{}) + + // compare the block + require.Equal(t, message["slot"], float64(c.slot)) + }) + } +} + +func TestGetBlockBlinded(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + // Start by testing + rootBlock1, err := blocks[0].Block.HashSSZ() + if err != nil { + t.Fatal(err) + } + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + + cases := []struct { + blockID string + code int + slot uint64 + }{ + { + blockID: "0x" + common.Bytes2Hex(rootBlock1[:]), + code: http.StatusOK, + slot: blocks[0].Block.Slot, + }, + { + blockID: "head", + code: http.StatusOK, + slot: blocks[len(blocks)-1].Block.Slot, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/blocks/{block_id} + resp, err := http.Get(server.URL + "/eth/v2/beacon/blocks/" + c.blockID) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + jsonVal := make(map[string]interface{}) + // unmarshal the json + require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal)) + data := jsonVal["data"].(map[string]interface{}) + message := data["message"].(map[string]interface{}) + + // compare the block + require.Equal(t, message["slot"], float64(c.slot)) + }) + } +} + +func TestGetBlockAttestations(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + // Start by testing + rootBlock1, err := blocks[0].Block.HashSSZ() + if err != nil { + t.Fatal(err) + } + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + + cases := []struct { + blockID string + code int + attLen int + }{ + { + blockID: "0x" + common.Bytes2Hex(rootBlock1[:]), + code: http.StatusOK, + attLen: blocks[0].Block.Body.Attestations.Len(), + }, + { + blockID: "head", + code: http.StatusOK, + attLen: blocks[len(blocks)-1].Block.Body.Attestations.Len(), + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/blocks/{block_id} + resp, err := http.Get(server.URL + "/eth/v1/beacon/blocks/" + c.blockID + "/attestations") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + jsonVal := make(map[string]interface{}) + // unmarshal the json + require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal)) + data := jsonVal["data"].([]interface{}) + require.Equal(t, len(data), c.attLen) + }) + } +} + +func TestGetBlockRoot(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + var err error + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + // compute block 0 and block len -1 root + blk0Root, err := blocks[0].Block.HashSSZ() + require.NoError(t, err) + + blkLastRoot, err := blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + cases := []struct { + blockID string + code int + root string + }{ + { + blockID: strconv.FormatInt(int64(blocks[0].Block.Slot), 10), + code: http.StatusOK, + root: "0x" + common.Bytes2Hex(blk0Root[:]), + }, + { + blockID: "head", + code: http.StatusOK, + root: "0x" + common.Bytes2Hex(blkLastRoot[:]), + }, + { + blockID: "19912929", + code: http.StatusNotFound, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/blocks/{block_id} + resp, err := http.Get(server.URL + "/eth/v1/beacon/blocks/" + c.blockID + "/root") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + jsonVal := make(map[string]interface{}) + // unmarshal the json + require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal)) + data := jsonVal["data"].(map[string]interface{}) + root := data["root"].(string) + require.Equal(t, root, c.root) + }) + } +} diff --git a/cl/beacon/handler/config_test.go b/cl/beacon/handler/config_test.go new file mode 100644 index 00000000000..20b7abc557b --- /dev/null +++ b/cl/beacon/handler/config_test.go @@ -0,0 +1,81 @@ +package handler + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/stretchr/testify/require" +) + +func TestGetSpec(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version) + + server := httptest.NewServer(handler.mux) + defer server.Close() + + resp, err := http.Get(server.URL + "/eth/v1/config/spec") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + out := make(map[string]interface{}) + err = json.NewDecoder(resp.Body).Decode(&out) + require.NoError(t, err) + + data := out["data"].(map[string]interface{}) + require.Equal(t, data["SlotsPerEpoch"], float64(32)) + require.Equal(t, data["SlotsPerHistoricalRoot"], float64(8192)) +} + +func TestGetForkSchedule(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version) + + server := httptest.NewServer(handler.mux) + defer server.Close() + + resp, err := http.Get(server.URL + "/eth/v1/config/fork_schedule") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + out := make(map[string]interface{}) + err = json.NewDecoder(resp.Body).Decode(&out) + require.NoError(t, err) + + require.Greater(t, len(out["data"].([]interface{})), 2) + for _, v := range out["data"].([]interface{}) { + data := v.(map[string]interface{}) + require.NotNil(t, data["current_version"]) + require.NotNil(t, data["epoch"]) + require.NotNil(t, data["previous_version"]) + } +} + +func TestGetDepositContract(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version) + + server := httptest.NewServer(handler.mux) + defer server.Close() + + resp, err := http.Get(server.URL + "/eth/v1/config/deposit_contract") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + out := make(map[string]interface{}) + err = json.NewDecoder(resp.Body).Decode(&out) + require.NoError(t, err) + + data := out["data"].(map[string]interface{}) + require.Equal(t, data["address"], "0x00000000219ab540356cBB839Cbe05303d7705Fa") + require.Equal(t, data["chain_id"], float64(1)) +} diff --git a/cl/beacon/handler/duties_proposer.go b/cl/beacon/handler/duties_proposer.go index 609a8292c41..a2c06ee60d0 100644 --- a/cl/beacon/handler/duties_proposer.go +++ b/cl/beacon/handler/duties_proposer.go @@ -7,9 +7,12 @@ import ( "sync" "github.com/ledgerwatch/erigon/cl/beacon/beaconhttp" + "github.com/ledgerwatch/erigon/cl/persistence/base_encoding" + state_accessors "github.com/ledgerwatch/erigon/cl/persistence/state" shuffling2 "github.com/ledgerwatch/erigon/cl/phase1/core/state/shuffling" libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/kv" ) type proposerDuties struct { @@ -19,21 +22,47 @@ type proposerDuties struct { } func (a *ApiHandler) getDutiesProposer(r *http.Request) (*beaconResponse, error) { - epoch, err := epochFromRequest(r) if err != nil { return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) } if epoch < a.forkchoiceStore.FinalizedCheckpoint().Epoch() { - return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, "invalid epoch") + tx, err := a.indiciesDB.BeginRo(r.Context()) + if err != nil { + return nil, err + } + defer tx.Rollback() + key := base_encoding.Encode64ToBytes4(epoch) + indiciesBytes, err := tx.GetOne(kv.Proposers, key) + if err != nil { + return nil, err + } + if len(indiciesBytes) != int(a.beaconChainCfg.SlotsPerEpoch*4) { + return nil, beaconhttp.NewEndpointError(http.StatusInternalServerError, "proposer duties is corrupted") + } + duties := make([]proposerDuties, a.beaconChainCfg.SlotsPerEpoch) + for i := uint64(0); i < a.beaconChainCfg.SlotsPerEpoch; i++ { + validatorIndex := binary.BigEndian.Uint32(indiciesBytes[i*4 : i*4+4]) + var pk libcommon.Bytes48 + pk, err := state_accessors.ReadPublicKeyByIndex(tx, uint64(validatorIndex)) + if err != nil { + return nil, err + } + duties[i] = proposerDuties{ + Pubkey: pk, + ValidatorIndex: uint64(validatorIndex), + Slot: epoch*a.beaconChainCfg.SlotsPerEpoch + i, + } + } + return newBeaconResponse(duties).withFinalized(true).withVersion(a.beaconChainCfg.GetCurrentStateVersion(epoch)), nil } // We need to compute our duties state, cancel := a.syncedData.HeadState() defer cancel() if state == nil { - return nil, beaconhttp.NewEndpointError(http.StatusInternalServerError, "beacon node is syncing") + return nil, beaconhttp.NewEndpointError(http.StatusServiceUnavailable, "beacon node is syncing") } @@ -90,5 +119,4 @@ func (a *ApiHandler) getDutiesProposer(r *http.Request) (*beaconResponse, error) wg.Wait() return newBeaconResponse(duties).withFinalized(false).withVersion(a.beaconChainCfg.GetCurrentStateVersion(epoch)), nil - } diff --git a/cl/beacon/handler/duties_proposer_test.go b/cl/beacon/handler/duties_proposer_test.go new file mode 100644 index 00000000000..bba6c93773e --- /dev/null +++ b/cl/beacon/handler/duties_proposer_test.go @@ -0,0 +1,112 @@ +package handler + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/stretchr/testify/require" +) + +func TestProposerDutiesProposerFcu(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, postState, _, handler, _, syncedDataManager, fcu := setupTestingHandler(t, clparams.Phase0Version) + epoch := blocks[len(blocks)-1].Block.Slot / 32 + + require.NoError(t, syncedDataManager.OnHeadState(postState)) + + fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{}, epoch) + + server := httptest.NewServer(handler.mux) + defer server.Close() + + resp, err := http.Get(server.URL + "/eth/v1/validator/duties/proposer/" + strconv.FormatUint(epoch, 10)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + out := make(map[string]interface{}) + err = json.NewDecoder(resp.Body).Decode(&out) + require.NoError(t, err) + + data := out["data"].([]interface{}) + require.Equal(t, len(data), 32) + for _, v := range data { + d := v.(map[string]interface{}) + require.NotNil(t, d["pubkey"]) + require.NotNil(t, d["validator_index"]) + require.NotNil(t, d["slot"]) + } +} + +func TestProposerDutiesProposerBadEpoch(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, _, _, postState, _, handler, _, syncedDataManager, fcu := setupTestingHandler(t, clparams.Phase0Version) + + require.NoError(t, syncedDataManager.OnHeadState(postState)) + + fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{}, 1) + + server := httptest.NewServer(handler.mux) + defer server.Close() + + resp, err := http.Get(server.URL + "/eth/v1/validator/duties/proposer/abc") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusBadRequest, resp.StatusCode) +} + +func TestProposerDutiesNotSynced(t *testing.T) { + _, _, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{}, 1) + + server := httptest.NewServer(handler.mux) + defer server.Close() + + resp, err := http.Get(server.URL + "/eth/v1/validator/duties/proposer/1") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode) +} + +func TestProposerDutiesProposerFcuHistorical(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, postState, _, handler, _, syncedDataManager, fcu := setupTestingHandler(t, clparams.Phase0Version) + epoch := blocks[len(blocks)-1].Block.Slot / 32 + + require.NoError(t, syncedDataManager.OnHeadState(postState)) + + fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(common.Hash{}, epoch) + + server := httptest.NewServer(handler.mux) + defer server.Close() + + resp, err := http.Get(server.URL + "/eth/v1/validator/duties/proposer/" + strconv.FormatUint(epoch-1, 10)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + out := make(map[string]interface{}) + err = json.NewDecoder(resp.Body).Decode(&out) + require.NoError(t, err) + + data := out["data"].([]interface{}) + require.Equal(t, len(data), 32) + for _, v := range data { + d := v.(map[string]interface{}) + require.NotNil(t, d["pubkey"]) + require.NotNil(t, d["validator_index"]) + require.NotNil(t, d["slot"]) + } +} diff --git a/cl/beacon/handler/format.go b/cl/beacon/handler/format.go index 9f2d5682a28..28628131306 100644 --- a/cl/beacon/handler/format.go +++ b/cl/beacon/handler/format.go @@ -260,3 +260,12 @@ func uint64FromQueryParams(r *http.Request, name string) (*uint64, error) { } return &num, nil } + +// decode a list of strings from the query params +func stringListFromQueryParams(r *http.Request, name string) ([]string, error) { + str := r.URL.Query().Get(name) + if str == "" { + return nil, nil + } + return regexp.MustCompile(`\s*,\s*`).Split(str, -1), nil +} diff --git a/cl/beacon/handler/genesis.go b/cl/beacon/handler/genesis.go index 05af01dd8b5..31e0ee93265 100644 --- a/cl/beacon/handler/genesis.go +++ b/cl/beacon/handler/genesis.go @@ -11,7 +11,7 @@ import ( type genesisResponse struct { GenesisTime uint64 `json:"genesis_time,omitempty"` - GenesisValidatorRoot common.Hash `json:"genesis_validator_root,omitempty"` + GenesisValidatorRoot common.Hash `json:"genesis_validators_root,omitempty"` GenesisForkVersion libcommon.Bytes4 `json:"genesis_fork_version,omitempty"` } diff --git a/cl/beacon/handler/genesis_test.go b/cl/beacon/handler/genesis_test.go new file mode 100644 index 00000000000..59cb3821dab --- /dev/null +++ b/cl/beacon/handler/genesis_test.go @@ -0,0 +1,35 @@ +package handler + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/stretchr/testify/require" +) + +func TestGetGenesis(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, _, _, _, _, handler, _, _, _ := setupTestingHandler(t, clparams.Phase0Version) + + server := httptest.NewServer(handler.mux) + defer server.Close() + + resp, err := http.Get(server.URL + "/eth/v1/beacon/genesis") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode) + out := make(map[string]interface{}) + err = json.NewDecoder(resp.Body).Decode(&out) + require.NoError(t, err) + + data := out["data"].(map[string]interface{}) + genesisTime := uint64(data["genesis_time"].(float64)) + require.Equal(t, genesisTime, uint64(1606824023)) + require.Equal(t, data["genesis_fork_version"], "0xbba4da96") + require.Equal(t, data["genesis_validators_root"], "0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95") +} diff --git a/cl/beacon/handler/handler.go b/cl/beacon/handler/handler.go index 36416e7bd06..bdd89189ff2 100644 --- a/cl/beacon/handler/handler.go +++ b/cl/beacon/handler/handler.go @@ -48,11 +48,10 @@ func (a *ApiHandler) init() { r.Get("/fork_schedule", beaconhttp.HandleEndpointFunc(a.getForkSchedule)) }) r.Route("/beacon", func(r chi.Router) { - // r.Route("/headers", func(r chi.Router) { - // r.Get("/", beaconhttp.HandleEndpointFunc(a.getHeaders)) - // r.Get("/{block_id}", beaconhttp.HandleEndpointFunc(a.getHeader)) - // }) - r.Get("/headers", beaconhttp.HandleEndpointFunc(a.getHeaders)) + r.Route("/headers", func(r chi.Router) { + r.Get("/", beaconhttp.HandleEndpointFunc(a.getHeaders)) + r.Get("/{block_id}", beaconhttp.HandleEndpointFunc(a.getHeader)) + }) r.Route("/blocks", func(r chi.Router) { r.Post("/", http.NotFound) r.Get("/{block_id}", beaconhttp.HandleEndpointFunc(a.getBlock)) @@ -80,6 +79,7 @@ func (a *ApiHandler) init() { r.Get("/validators", http.NotFound) r.Get("/root", beaconhttp.HandleEndpointFunc(a.getStateRoot)) r.Get("/fork", beaconhttp.HandleEndpointFunc(a.getStateFork)) + r.Get("/validators", beaconhttp.HandleEndpointFunc(a.getAllValidators)) r.Get("/validators/{id}", http.NotFound) }) }) @@ -108,7 +108,7 @@ func (a *ApiHandler) init() { }) }) r.Route("/beacon", func(r chi.Router) { - r.Get("/blocks/{block_id}", beaconhttp.HandleEndpointFunc(a.getBlock)) //otterscan + r.Get("/blocks/{block_id}", beaconhttp.HandleEndpointFunc(a.getBlock)) }) r.Route("/validator", func(r chi.Router) { r.Post("/blocks/{slot}", http.NotFound) diff --git a/cl/beacon/handler/headers_test.go b/cl/beacon/handler/headers_test.go new file mode 100644 index 00000000000..36349ef2991 --- /dev/null +++ b/cl/beacon/handler/headers_test.go @@ -0,0 +1,178 @@ +package handler + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/common" + "github.com/stretchr/testify/require" +) + +func TestGetHeader(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + // Start by testing + rootBlock1, err := blocks[0].Block.HashSSZ() + if err != nil { + t.Fatal(err) + } + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + + bodyRoot1, err := blocks[0].Block.Body.HashSSZ() + require.NoError(t, err) + + bodyRoot2, err := blocks[len(blocks)-1].Block.Body.HashSSZ() + require.NoError(t, err) + + cases := []struct { + blockID string + code int + slot uint64 + bodyRoot string + }{ + { + blockID: "0x" + common.Bytes2Hex(rootBlock1[:]), + code: http.StatusOK, + slot: blocks[0].Block.Slot, + bodyRoot: "0x" + common.Bytes2Hex(bodyRoot1[:]), + }, + { + blockID: "head", + code: http.StatusOK, + slot: blocks[len(blocks)-1].Block.Slot, + bodyRoot: "0x" + common.Bytes2Hex(bodyRoot2[:]), + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/blocks/{block_id} + resp, err := http.Get(server.URL + "/eth/v1/beacon/headers/" + c.blockID) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + jsonVal := make(map[string]interface{}) + // unmarshal the json + require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal)) + data := jsonVal["data"].(map[string]interface{}) + header := data["header"].(map[string]interface{}) + message := header["message"].(map[string]interface{}) + + // compare the block + require.Equal(t, message["slot"], float64(c.slot)) + require.Equal(t, message["body_root"], c.bodyRoot) + require.Equal(t, data["canonical"], true) + }) + } +} + +func TestGetHeaders(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, _, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + var err error + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + + bodyRoot1, err := blocks[0].Block.Body.HashSSZ() + require.NoError(t, err) + + bodyRoot2, err := blocks[len(blocks)-1].Block.Body.HashSSZ() + require.NoError(t, err) + + cases := []struct { + name string + code int + slotReq *uint64 + parentRoot *libcommon.Hash + slot uint64 + bodyRoot string + count int + }{ + { + count: 1, + name: "slot", + code: http.StatusOK, + slotReq: &blocks[0].Block.Slot, + slot: blocks[0].Block.Slot, + bodyRoot: "0x" + common.Bytes2Hex(bodyRoot1[:]), + }, + { + count: 0, + name: "none", + code: http.StatusOK, + slot: blocks[len(blocks)-1].Block.Slot, + bodyRoot: "0x" + common.Bytes2Hex(bodyRoot2[:]), + }, + { + count: 0, + name: "parent", + code: http.StatusOK, + slotReq: &blocks[0].Block.Slot, + slot: blocks[0].Block.Slot, + parentRoot: &blocks[0].Block.ParentRoot, + bodyRoot: "0x" + common.Bytes2Hex(bodyRoot1[:]), + }, + { + count: 0, + name: "wtf", + code: http.StatusOK, + slotReq: new(uint64), + slot: blocks[0].Block.Slot, + parentRoot: &blocks[0].Block.ParentRoot, + bodyRoot: "0x" + common.Bytes2Hex(bodyRoot1[:]), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + url := server.URL + "/eth/v1/beacon/headers?lol=0" // lol is a random query param + + if c.slotReq != nil { + url += "&slot=" + strconv.FormatInt(int64(*c.slotReq), 10) + } + if c.parentRoot != nil { + url += "&parent_root=" + "0x" + common.Bytes2Hex(c.parentRoot[:]) + } + // Query the block in the handler with /eth/v2/beacon/blocks/{block_id} + resp, err := http.Get(url) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + jsonVal := make(map[string]interface{}) + // unmarshal the json + require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal)) + data := jsonVal["data"].([]interface{}) + require.Equal(t, len(data), c.count) + }) + } +} diff --git a/cl/beacon/handler/states.go b/cl/beacon/handler/states.go index bedc8c21c9c..41bedae8ded 100644 --- a/cl/beacon/handler/states.go +++ b/cl/beacon/handler/states.go @@ -16,48 +16,47 @@ import ( "github.com/ledgerwatch/erigon/cl/utils" ) -func (a *ApiHandler) rootFromStateId(ctx context.Context, tx kv.Tx, stateId *segmentID) (root libcommon.Hash, httpStatusErr int, err error) { - var blockRoot libcommon.Hash +func (a *ApiHandler) blockRootFromStateId(ctx context.Context, tx kv.Tx, stateId *segmentID) (root libcommon.Hash, httpStatusErr int, err error) { switch { case stateId.head(): - blockRoot, _, err = a.forkchoiceStore.GetHead() + root, _, err = a.forkchoiceStore.GetHead() if err != nil { return libcommon.Hash{}, http.StatusInternalServerError, err } + return case stateId.finalized(): - blockRoot = a.forkchoiceStore.FinalizedCheckpoint().BlockRoot() + root = a.forkchoiceStore.FinalizedCheckpoint().BlockRoot() + return case stateId.justified(): - blockRoot = a.forkchoiceStore.JustifiedCheckpoint().BlockRoot() + root = a.forkchoiceStore.JustifiedCheckpoint().BlockRoot() + return case stateId.genesis(): - blockRoot, err = beacon_indicies.ReadCanonicalBlockRoot(tx, 0) + root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, 0) if err != nil { return libcommon.Hash{}, http.StatusInternalServerError, err } - if blockRoot == (libcommon.Hash{}) { + if root == (libcommon.Hash{}) { return libcommon.Hash{}, http.StatusNotFound, fmt.Errorf("genesis block not found") } + return case stateId.getSlot() != nil: - blockRoot, err = beacon_indicies.ReadCanonicalBlockRoot(tx, *stateId.getSlot()) + root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, *stateId.getSlot()) if err != nil { return libcommon.Hash{}, http.StatusInternalServerError, err } - if blockRoot == (libcommon.Hash{}) { + if root == (libcommon.Hash{}) { return libcommon.Hash{}, http.StatusNotFound, fmt.Errorf("block not found %d", *stateId.getSlot()) } + return case stateId.getRoot() != nil: - root = *stateId.getRoot() + root, err = beacon_indicies.ReadBlockRootByStateRoot(tx, *stateId.getRoot()) + if err != nil { + return libcommon.Hash{}, http.StatusInternalServerError, err + } return default: return libcommon.Hash{}, http.StatusInternalServerError, fmt.Errorf("cannot parse state id") } - root, err = beacon_indicies.ReadStateRootByBlockRoot(ctx, tx, blockRoot) - if err != nil { - return libcommon.Hash{}, http.StatusInternalServerError, err - } - if root == (libcommon.Hash{}) { - return libcommon.Hash{}, http.StatusNotFound, fmt.Errorf("block not found") - } - return } type rootResponse struct { @@ -84,7 +83,7 @@ func (a *ApiHandler) getStateFork(r *http.Request) (*beaconResponse, error) { if err != nil { return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) } - root, httpStatus, err := a.rootFromStateId(ctx, tx, blockId) + root, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId) if err != nil { return nil, beaconhttp.NewEndpointError(httpStatus, err.Error()) } @@ -94,7 +93,7 @@ func (a *ApiHandler) getStateFork(r *http.Request) (*beaconResponse, error) { return nil, err } if slot == nil { - return nil, beaconhttp.NewEndpointError(http.StatusNotFound, err.Error()) + return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block slot: %x", root)) } epoch := *slot / a.beaconChainCfg.SlotsPerEpoch @@ -123,7 +122,7 @@ func (a *ApiHandler) getStateRoot(r *http.Request) (*beaconResponse, error) { if err != nil { return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) } - root, httpStatus, err := a.rootFromStateId(ctx, tx, blockId) + root, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId) if err != nil { return nil, beaconhttp.NewEndpointError(httpStatus, err.Error()) } @@ -166,20 +165,40 @@ func (a *ApiHandler) getFullState(r *http.Request) (*beaconResponse, error) { return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) } - root, httpStatus, err := a.rootFromStateId(ctx, tx, blockId) + blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId) if err != nil { return nil, beaconhttp.NewEndpointError(httpStatus, err.Error()) } - blockRoot, err := beacon_indicies.ReadBlockRootByStateRoot(tx, root) - if err != nil { - return nil, err - } - state, err := a.forkchoiceStore.GetStateAtBlockRoot(blockRoot, true) if err != nil { return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) } + if state == nil { + slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot) + if err != nil { + return nil, err + } + // Sanity checks slot and canonical data. + if slot == nil { + return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block slot: %x", blockRoot)) + } + canonicalRoot, err := beacon_indicies.ReadCanonicalBlockRoot(tx, *slot) + if err != nil { + return nil, err + } + if canonicalRoot != blockRoot { + return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read state: %x", blockRoot)) + } + state, err := a.stateReader.ReadHistoricalState(ctx, tx, *slot) + if err != nil { + return nil, err + } + if state == nil { + return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read state: %x", blockRoot)) + } + return newBeaconResponse(state).withFinalized(true).withVersion(state.Version()), nil + } return newBeaconResponse(state).withFinalized(false).withVersion(state.Version()), nil } @@ -203,16 +222,11 @@ func (a *ApiHandler) getFinalityCheckpoints(r *http.Request) (*beaconResponse, e return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) } - root, httpStatus, err := a.rootFromStateId(ctx, tx, blockId) + blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId) if err != nil { return nil, beaconhttp.NewEndpointError(httpStatus, err.Error()) } - blockRoot, err := beacon_indicies.ReadBlockRootByStateRoot(tx, root) - if err != nil { - return nil, err - } - slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot) if err != nil { return nil, err @@ -244,7 +258,7 @@ func (a *ApiHandler) getFinalityCheckpoints(r *http.Request) (*beaconResponse, e FinalizedCheckpoint: finalizedCheckpoint, CurrentJustifiedCheckpoint: currentJustifiedCheckpoint, PreviousJustifiedCheckpoint: previousJustifiedCheckpoint, - }).withFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()).withVersion(version), nil + }).withFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()).withVersion(version), nil } type syncCommitteesResponse struct { @@ -265,16 +279,11 @@ func (a *ApiHandler) getSyncCommittees(r *http.Request) (*beaconResponse, error) return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) } - root, httpStatus, err := a.rootFromStateId(ctx, tx, blockId) + blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId) if err != nil { return nil, beaconhttp.NewEndpointError(httpStatus, err.Error()) } - blockRoot, err := beacon_indicies.ReadBlockRootByStateRoot(tx, root) - if err != nil { - return nil, err - } - slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot) if err != nil { return nil, err @@ -341,5 +350,5 @@ func (a *ApiHandler) getSyncCommittees(r *http.Request) (*beaconResponse, error) return nil, err } - return newBeaconResponse(response).withFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()), nil + return newBeaconResponse(response).withFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()), nil } diff --git a/cl/beacon/handler/states_test.go b/cl/beacon/handler/states_test.go new file mode 100644 index 00000000000..61cddbcba0e --- /dev/null +++ b/cl/beacon/handler/states_test.go @@ -0,0 +1,452 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/common" + "github.com/stretchr/testify/require" +) + +func TestGetStateFork(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + postRoot, err := postState.HashSSZ() + require.NoError(t, err) + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + fmt.Println(fcu.HeadSlotVal) + + cases := []struct { + blockID string + code int + }{ + { + blockID: "0x" + common.Bytes2Hex(postRoot[:]), + code: http.StatusOK, + }, + { + blockID: "head", + code: http.StatusOK, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + { + blockID: strconv.FormatInt(int64(postState.Slot()), 10), + code: http.StatusOK, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/blocks/{block_id} + resp, err := http.Get(server.URL + "/eth/v1/beacon/states/" + c.blockID + "/fork") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + jsonVal := make(map[string]interface{}) + // unmarshal the json + require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal)) + data := jsonVal["data"].(map[string]interface{}) + require.Equal(t, data["current_version"], "0x00000000") + require.Equal(t, data["previous_version"], "0x00000000") + require.Equal(t, data["epoch"], float64(0)) + }) + } +} + +func TestGetStateRoot(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + postRoot, err := postState.HashSSZ() + require.NoError(t, err) + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + fmt.Println(fcu.HeadSlotVal) + + fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32) + + cases := []struct { + blockID string + code int + }{ + { + blockID: "0x" + common.Bytes2Hex(postRoot[:]), + code: http.StatusOK, + }, + { + blockID: "finalized", + code: http.StatusOK, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + { + blockID: strconv.FormatInt(int64(postState.Slot()), 10), + code: http.StatusOK, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/blocks/{block_id} + resp, err := http.Get(server.URL + "/eth/v1/beacon/states/" + c.blockID + "/root") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + jsonVal := make(map[string]interface{}) + // unmarshal the json + require.NoError(t, json.NewDecoder(resp.Body).Decode(&jsonVal)) + data := jsonVal["data"].(map[string]interface{}) + require.Equal(t, data["root"], "0x"+common.Bytes2Hex(postRoot[:])) + }) + } +} + +func TestGetStateFullHistorical(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + postRoot, err := postState.HashSSZ() + require.NoError(t, err) + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + fmt.Println(fcu.HeadSlotVal) + + fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32) + + cases := []struct { + blockID string + code int + }{ + { + blockID: "0x" + common.Bytes2Hex(postRoot[:]), + code: http.StatusOK, + }, + { + blockID: "finalized", + code: http.StatusOK, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + { + blockID: strconv.FormatInt(int64(postState.Slot()), 10), + code: http.StatusOK, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream + req, err := http.NewRequest("GET", server.URL+"/eth/v2/debug/beacon/states/"+c.blockID, nil) + require.NoError(t, err) + req.Header.Set("Accept", "application/octet-stream") + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + // read the all of the octect + out, err := io.ReadAll(resp.Body) + require.NoError(t, err) + other := state.New(&clparams.MainnetBeaconConfig) + require.NoError(t, other.DecodeSSZ(out, int(clparams.Phase0Version))) + + otherRoot, err := other.HashSSZ() + require.NoError(t, err) + require.Equal(t, postRoot, otherRoot) + }) + } +} + +func TestGetStateFullForkchoice(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) + + postRoot, err := postState.HashSSZ() + require.NoError(t, err) + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + fmt.Println(fcu.HeadSlotVal) + + fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32) + + fcu.StateAtBlockRootVal[fcu.HeadVal] = postState + + cases := []struct { + blockID string + code int + }{ + { + blockID: "0x" + common.Bytes2Hex(postRoot[:]), + code: http.StatusOK, + }, + { + blockID: "finalized", + code: http.StatusOK, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + { + blockID: strconv.FormatInt(int64(postState.Slot()), 10), + code: http.StatusOK, + }, + } + + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + // Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream + req, err := http.NewRequest("GET", server.URL+"/eth/v2/debug/beacon/states/"+c.blockID, nil) + require.NoError(t, err) + req.Header.Set("Accept", "application/octet-stream") + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + // read the all of the octect + out, err := io.ReadAll(resp.Body) + require.NoError(t, err) + other := state.New(&clparams.MainnetBeaconConfig) + require.NoError(t, other.DecodeSSZ(out, int(clparams.Phase0Version))) + + otherRoot, err := other.HashSSZ() + require.NoError(t, err) + require.Equal(t, postRoot, otherRoot) + }) + } +} + +func TestGetStateSyncCommittees(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.BellatrixVersion) + + postRoot, err := postState.HashSSZ() + require.NoError(t, err) + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + + cSyncCommittee := postState.CurrentSyncCommittee().Copy() + nSyncCommittee := postState.NextSyncCommittee().Copy() + + fcu.GetSyncCommitteesVal[fcu.HeadVal] = [2]*solid.SyncCommittee{ + cSyncCommittee, + nSyncCommittee, + } + + fcu.JustifiedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32) + + cases := []struct { + blockID string + code int + }{ + { + blockID: "0x" + common.Bytes2Hex(postRoot[:]), + code: http.StatusOK, + }, + { + blockID: "justified", + code: http.StatusOK, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + { + blockID: strconv.FormatInt(int64(postState.Slot()), 10), + code: http.StatusOK, + }, + } + expected := `{"data":{"validators":[109,134,145,89,181,81,159,168,34,251,3,205,213,202,99,121,80,149,18,65,201,227,116,69,100,74,160,198,16,131,0,73,210,122,209,217,97,237,136,98,229,248,176,95,150,171,238,191,200,220,33,219,126,9,214,124,56,86,169,208,125,85,25,88,13,190,153,183,96,165,180,90,164,104,240,123,118,196,163,222,231,127,241,77,68,32,62,79,44,58,14,187,151,243,139,142,174,106,228,102,223,31,120,5,43,255,179,66,119,170,60,152,167,194,4,112,156,233,254,203,1,55,53,19,92,21,28,42,141,162,146,57,23,45,158,93,212,38,2,206,246,225,195,189,47,193,224,242,76,138,84,140,111,51,135,113,41,133,207,30,82,175,161,6,249,83,234,155,244,177,108,252,94,143,173,8,154,75,50,49,39,36,182,101,48,12,172,87,250,59,24,157,215,218,72,185,71,7,253,114,230,226,110,46,166,91,130,20,137,117,132,204,221,52,197,188,11,232,67,115,245,26,35,103,186,37,27,235,64,40,70,239,236,211,61,29,216,199,63,54,78,105,184,15,10,147,247,22,144,107,128,17,178,148,129,192,109,134,145,89,181,81,159,168,34,251,3,205,213,202,99,121,80,149,18,65,201,227,116,69,100,74,160,198,16,131,0,73,210,122,209,217,97,237,136,98,229,248,176,95,150,171,238,191,200,220,33,219,126,9,214,124,56,86,169,208,125,85,25,88,13,190,153,183,96,165,180,90,164,104,240,123,118,196,163,222,231,127,241,77,68,32,62,79,44,58,14,187,151,243,139,142,174,106,228,102,223,31,120,5,43,255,179,66,119,170,60,152,167,194,4,112,156,233,254,203,1,55,53,19,92,21,28,42,141,162,146,57,23,45,158,93,212,38,2,206,246,225,195,189,47,193,224,242,76,138,84,140,111,51,135,113,41,133,207,30,82,175,161,6,249,83,234,155,244,177,108,252,94,143,173,8,154,75,50,49,39,36,182,101,48,12,172,87,250,59,24,157,215,218,72,185,71,7,253,114,230,226,110,46,166,91,130,20,137,117,132,204,221,52,197,188,11,232,67,115,245,26,35,103,186,37,27,235,64,40,70,239,236,211,61,29,216,199,63,54,78,105,184,15,10,147,247,22,144,107,128,17,178,148,129,192],"validator_aggregates":[[109,134,145,89,181,81,159,168,34,251,3,205,213,202,99,121,80,149,18,65,201,227,116,69,100,74,160,198,16,131,0,73,210,122,209,217,97,237,136,98,229,248,176,95,150,171,238,191,200,220,33,219,126,9,214,124,56,86,169,208,125,85,25,88,13,190,153,183,96,165,180,90,164,104,240,123,118,196,163,222,231,127,241,77,68,32,62,79,44,58,14,187,151,243,139,142,174,106,228,102,223,31,120,5,43,255,179,66,119,170,60,152,167,194,4,112,156,233,254,203,1,55,53,19,92,21,28,42],[141,162,146,57,23,45,158,93,212,38,2,206,246,225,195,189,47,193,224,242,76,138,84,140,111,51,135,113,41,133,207,30,82,175,161,6,249,83,234,155,244,177,108,252,94,143,173,8,154,75,50,49,39,36,182,101,48,12,172,87,250,59,24,157,215,218,72,185,71,7,253,114,230,226,110,46,166,91,130,20,137,117,132,204,221,52,197,188,11,232,67,115,245,26,35,103,186,37,27,235,64,40,70,239,236,211,61,29,216,199,63,54,78,105,184,15,10,147,247,22,144,107,128,17,178,148,129,192],[109,134,145,89,181,81,159,168,34,251,3,205,213,202,99,121,80,149,18,65,201,227,116,69,100,74,160,198,16,131,0,73,210,122,209,217,97,237,136,98,229,248,176,95,150,171,238,191,200,220,33,219,126,9,214,124,56,86,169,208,125,85,25,88,13,190,153,183,96,165,180,90,164,104,240,123,118,196,163,222,231,127,241,77,68,32,62,79,44,58,14,187,151,243,139,142,174,106,228,102,223,31,120,5,43,255,179,66,119,170,60,152,167,194,4,112,156,233,254,203,1,55,53,19,92,21,28,42],[141,162,146,57,23,45,158,93,212,38,2,206,246,225,195,189,47,193,224,242,76,138,84,140,111,51,135,113,41,133,207,30,82,175,161,6,249,83,234,155,244,177,108,252,94,143,173,8,154,75,50,49,39,36,182,101,48,12,172,87,250,59,24,157,215,218,72,185,71,7,253,114,230,226,110,46,166,91,130,20,137,117,132,204,221,52,197,188,11,232,67,115,245,26,35,103,186,37,27,235,64,40,70,239,236,211,61,29,216,199,63,54,78,105,184,15,10,147,247,22,144,107,128,17,178,148,129,192]]},"finalized":false,"execution_optimistic":false}` + "\n" + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + resp, err := http.Get(server.URL + "/eth/v1/beacon/states/" + c.blockID + "/sync_committees") + require.NoError(t, err) + + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + // read the all of the octect + out, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, string(out), expected) + }) + } +} + +func TestGetStateSyncCommitteesHistorical(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.BellatrixVersion) + + postRoot, err := postState.HashSSZ() + require.NoError(t, err) + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + + fcu.JustifiedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32) + + cases := []struct { + blockID string + code int + }{ + { + blockID: "0x" + common.Bytes2Hex(postRoot[:]), + code: http.StatusOK, + }, + { + blockID: "justified", + code: http.StatusOK, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + { + blockID: strconv.FormatInt(int64(postState.Slot()), 10), + code: http.StatusOK, + }, + } + expected := `{"data":{"validators":[109,134,145,89,181,81,159,168,34,251,3,205,213,202,99,121,80,149,18,65,201,227,116,69,100,74,160,198,16,131,0,73,210,122,209,217,97,237,136,98,229,248,176,95,150,171,238,191,200,220,33,219,126,9,214,124,56,86,169,208,125,85,25,88,13,190,153,183,96,165,180,90,164,104,240,123,118,196,163,222,231,127,241,77,68,32,62,79,44,58,14,187,151,243,139,142,174,106,228,102,223,31,120,5,43,255,179,66,119,170,60,152,167,194,4,112,156,233,254,203,1,55,53,19,92,21,28,42,141,162,146,57,23,45,158,93,212,38,2,206,246,225,195,189,47,193,224,242,76,138,84,140,111,51,135,113,41,133,207,30,82,175,161,6,249,83,234,155,244,177,108,252,94,143,173,8,154,75,50,49,39,36,182,101,48,12,172,87,250,59,24,157,215,218,72,185,71,7,253,114,230,226,110,46,166,91,130,20,137,117,132,204,221,52,197,188,11,232,67,115,245,26,35,103,186,37,27,235,64,40,70,239,236,211,61,29,216,199,63,54,78,105,184,15,10,147,247,22,144,107,128,17,178,148,129,192,109,134,145,89,181,81,159,168,34,251,3,205,213,202,99,121,80,149,18,65,201,227,116,69,100,74,160,198,16,131,0,73,210,122,209,217,97,237,136,98,229,248,176,95,150,171,238,191,200,220,33,219,126,9,214,124,56,86,169,208,125,85,25,88,13,190,153,183,96,165,180,90,164,104,240,123,118,196,163,222,231,127,241,77,68,32,62,79,44,58,14,187,151,243,139,142,174,106,228,102,223,31,120,5,43,255,179,66,119,170,60,152,167,194,4,112,156,233,254,203,1,55,53,19,92,21,28,42,141,162,146,57,23,45,158,93,212,38,2,206,246,225,195,189,47,193,224,242,76,138,84,140,111,51,135,113,41,133,207,30,82,175,161,6,249,83,234,155,244,177,108,252,94,143,173,8,154,75,50,49,39,36,182,101,48,12,172,87,250,59,24,157,215,218,72,185,71,7,253,114,230,226,110,46,166,91,130,20,137,117,132,204,221,52,197,188,11,232,67,115,245,26,35,103,186,37,27,235,64,40,70,239,236,211,61,29,216,199,63,54,78,105,184,15,10,147,247,22,144,107,128,17,178,148,129,192],"validator_aggregates":[[109,134,145,89,181,81,159,168,34,251,3,205,213,202,99,121,80,149,18,65,201,227,116,69,100,74,160,198,16,131,0,73,210,122,209,217,97,237,136,98,229,248,176,95,150,171,238,191,200,220,33,219,126,9,214,124,56,86,169,208,125,85,25,88,13,190,153,183,96,165,180,90,164,104,240,123,118,196,163,222,231,127,241,77,68,32,62,79,44,58,14,187,151,243,139,142,174,106,228,102,223,31,120,5,43,255,179,66,119,170,60,152,167,194,4,112,156,233,254,203,1,55,53,19,92,21,28,42],[141,162,146,57,23,45,158,93,212,38,2,206,246,225,195,189,47,193,224,242,76,138,84,140,111,51,135,113,41,133,207,30,82,175,161,6,249,83,234,155,244,177,108,252,94,143,173,8,154,75,50,49,39,36,182,101,48,12,172,87,250,59,24,157,215,218,72,185,71,7,253,114,230,226,110,46,166,91,130,20,137,117,132,204,221,52,197,188,11,232,67,115,245,26,35,103,186,37,27,235,64,40,70,239,236,211,61,29,216,199,63,54,78,105,184,15,10,147,247,22,144,107,128,17,178,148,129,192],[109,134,145,89,181,81,159,168,34,251,3,205,213,202,99,121,80,149,18,65,201,227,116,69,100,74,160,198,16,131,0,73,210,122,209,217,97,237,136,98,229,248,176,95,150,171,238,191,200,220,33,219,126,9,214,124,56,86,169,208,125,85,25,88,13,190,153,183,96,165,180,90,164,104,240,123,118,196,163,222,231,127,241,77,68,32,62,79,44,58,14,187,151,243,139,142,174,106,228,102,223,31,120,5,43,255,179,66,119,170,60,152,167,194,4,112,156,233,254,203,1,55,53,19,92,21,28,42],[141,162,146,57,23,45,158,93,212,38,2,206,246,225,195,189,47,193,224,242,76,138,84,140,111,51,135,113,41,133,207,30,82,175,161,6,249,83,234,155,244,177,108,252,94,143,173,8,154,75,50,49,39,36,182,101,48,12,172,87,250,59,24,157,215,218,72,185,71,7,253,114,230,226,110,46,166,91,130,20,137,117,132,204,221,52,197,188,11,232,67,115,245,26,35,103,186,37,27,235,64,40,70,239,236,211,61,29,216,199,63,54,78,105,184,15,10,147,247,22,144,107,128,17,178,148,129,192]]},"finalized":false,"execution_optimistic":false}` + "\n" + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + resp, err := http.Get(server.URL + "/eth/v1/beacon/states/" + c.blockID + "/sync_committees") + require.NoError(t, err) + + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + // read the all of the octect + out, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, string(out), expected) + }) + } +} + +func TestGetStateFinalityCheckpoints(t *testing.T) { + + // setupTestingHandler(t, clparams.Phase0Version) + _, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.BellatrixVersion) + + postRoot, err := postState.HashSSZ() + require.NoError(t, err) + + fcu.HeadVal, err = blocks[len(blocks)-1].Block.HashSSZ() + require.NoError(t, err) + + fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot + + fcu.JustifiedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32) + + cases := []struct { + blockID string + code int + }{ + { + blockID: "0x" + common.Bytes2Hex(postRoot[:]), + code: http.StatusOK, + }, + { + blockID: "justified", + code: http.StatusOK, + }, + { + blockID: "0x" + common.Bytes2Hex(make([]byte, 32)), + code: http.StatusNotFound, + }, + { + blockID: strconv.FormatInt(int64(postState.Slot()), 10), + code: http.StatusOK, + }, + } + expected := `{"data":{"finalized_checkpoint":{"epoch":1,"root":"0xde46b0f2ed5e72f0cec20246403b14c963ec995d7c2825f3532b0460c09d5693"},"current_justified_checkpoint":{"epoch":3,"root":"0xa6e47f164b1a3ca30ea3b2144bd14711de442f51e5b634750a12a1734e24c987"},"previous_justified_checkpoint":{"epoch":2,"root":"0x4c3ee7969e485696669498a88c17f70e6999c40603e2f4338869004392069063"}},"finalized":false,"version":0,"execution_optimistic":false}` + "\n" + for _, c := range cases { + t.Run(c.blockID, func(t *testing.T) { + server := httptest.NewServer(handler.mux) + defer server.Close() + resp, err := http.Get(server.URL + "/eth/v1/beacon/states/" + c.blockID + "/finality_checkpoints") + require.NoError(t, err) + + defer resp.Body.Close() + require.Equal(t, c.code, resp.StatusCode) + if resp.StatusCode != http.StatusOK { + return + } + // read the all of the octect + out, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, string(out), expected) + }) + } +} diff --git a/cl/beacon/handler/utils_test.go b/cl/beacon/handler/utils_test.go new file mode 100644 index 00000000000..8f513ac7f66 --- /dev/null +++ b/cl/beacon/handler/utils_test.go @@ -0,0 +1,64 @@ +package handler + +import ( + "context" + "testing" + + "github.com/ledgerwatch/erigon-lib/common/datadir" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/memdb" + "github.com/ledgerwatch/erigon/cl/antiquary" + "github.com/ledgerwatch/erigon/cl/antiquary/tests" + "github.com/ledgerwatch/erigon/cl/beacon/synced_data" + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/persistence" + state_accessors "github.com/ledgerwatch/erigon/cl/persistence/state" + "github.com/ledgerwatch/erigon/cl/persistence/state/historical_states_reader" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/phase1/forkchoice" + "github.com/ledgerwatch/erigon/cl/pool" + "github.com/ledgerwatch/log/v3" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func setupTestingHandler(t *testing.T, v clparams.StateVersion) (db kv.RwDB, blocks []*cltypes.SignedBeaconBlock, f afero.Fs, preState, postState *state.CachingBeaconState, handler *ApiHandler, opPool pool.OperationsPool, syncedData *synced_data.SyncedDataManager, fcu *forkchoice.ForkChoiceStorageMock) { + if v == clparams.Phase0Version { + blocks, preState, postState = tests.GetPhase0Random() + } else if v == clparams.BellatrixVersion { + blocks, preState, postState = tests.GetBellatrixRandom() + } else { + require.FailNow(t, "unknown state version") + } + fcu = forkchoice.NewForkChoiceStorageMock() + db = memdb.NewTestDB(t) + var reader *tests.MockBlockReader + reader, f = tests.LoadChain(blocks, postState, db, t) + + rawDB := persistence.NewAferoRawBlockSaver(f, &clparams.MainnetBeaconConfig) + + bcfg := clparams.MainnetBeaconConfig + bcfg.InitializeForkSchedule() + ctx := context.Background() + vt := state_accessors.NewStaticValidatorTable() + a := antiquary.NewAntiquary(ctx, preState, vt, &bcfg, datadir.New("/tmp"), nil, db, nil, reader, nil, log.New(), true, true, f) + require.NoError(t, a.IncrementBeaconState(ctx, blocks[len(blocks)-1].Block.Slot+33)) + // historical states reader below + statesReader := historical_states_reader.NewHistoricalStatesReader(&bcfg, reader, vt, f, preState) + opPool = pool.NewOperationsPool(&bcfg) + syncedData = synced_data.NewSyncedDataManager(true, &bcfg) + gC := clparams.GenesisConfigs[clparams.MainnetNetwork] + handler = NewApiHandler( + &gC, + &bcfg, + rawDB, + db, + fcu, + opPool, + reader, + syncedData, + statesReader) + handler.init() + return +} diff --git a/cl/beacon/handler/validators.go b/cl/beacon/handler/validators.go new file mode 100644 index 00000000000..cbacef3c5eb --- /dev/null +++ b/cl/beacon/handler/validators.go @@ -0,0 +1,128 @@ +package handler + +import ( + "encoding/hex" + "net/http" + "strconv" + + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/beacon/beaconhttp" + state_accessors "github.com/ledgerwatch/erigon/cl/persistence/state" +) + +type validatorStatus string + +const ( + validatorPendingInitialized validatorStatus = "pending_initialized" + validatorPendingQueued validatorStatus = "pending_queued" + validatorActiveOngoing validatorStatus = "active_ongoing" + validatorActiveExiting validatorStatus = "active_exiting" + validatorActiveSlashed validatorStatus = "active_slashed" + validatorExitedUnslashed validatorStatus = "exited_unslashed" + validatorExitedSlashed validatorStatus = "exited_slashed" + validatorWithdrawalPossible validatorStatus = "withdrawal_possible" + validatorWithdrawalDone validatorStatus = "withdrawal_done" + validatorActive validatorStatus = "active" + validatorPending validatorStatus = "pending" + validatorExited validatorStatus = "exited" + validatorWithdrawal validatorStatus = "withdrawal" +) + +const maxValidatorsLookupFilter = 32 + +func checkValidStatus(s string) *beaconhttp.EndpointError { + switch validatorStatus(s) { + case validatorPendingInitialized, validatorPendingQueued, validatorActiveOngoing, validatorActiveExiting, validatorActiveSlashed, validatorExitedUnslashed, validatorExitedSlashed, validatorWithdrawalPossible, validatorWithdrawalDone, validatorActive, validatorPending, validatorExited, validatorWithdrawal: + return nil + default: + return beaconhttp.NewEndpointError(http.StatusBadRequest, "invalid validator status") + } +} + +func checkValidValidatorId(s string) (bool, *beaconhttp.EndpointError) { + // If it starts with 0x, then it must a 48bytes 0x prefixed string + if len(s) == 66 && s[:2] == "0x" { + // check if it is a valid hex string + if _, err := hex.DecodeString(s[2:]); err != nil { + return false, beaconhttp.NewEndpointError(http.StatusBadRequest, "invalid validator id") + } + return true, nil + } + // If it is not 0x prefixed, then it must be a number, check if it is a base-10 number + if _, err := strconv.ParseUint(s, 10, 64); err != nil { + return false, beaconhttp.NewEndpointError(http.StatusBadRequest, "invalid validator id") + } + return false, nil +} + +func (a *ApiHandler) getAllValidators(r *http.Request) (*beaconResponse, error) { + ctx := r.Context() + + tx, err := a.indiciesDB.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + + blockId, err := stateIdFromRequest(r) + if err != nil { + return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) + } + + blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId) + if err != nil { + return nil, beaconhttp.NewEndpointError(httpStatus, err.Error()) + } + + statusFilters, err := stringListFromQueryParams(r, "status") + if err != nil { + return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) + } + + validatorIds, err := stringListFromQueryParams(r, "id") + if err != nil { + return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) + } + + if len(validatorIds) > maxValidatorsLookupFilter { + return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, "too many validators requested") + } + filterIndicies := make([]uint64, 0, len(validatorIds)) + + for _, id := range validatorIds { + isPublicKey, err := checkValidValidatorId(id) + if err != nil { + return nil, err + } + if isPublicKey { + var b48 libcommon.Bytes48 + if err := b48.UnmarshalText([]byte(id[2:])); err != nil { + return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) + } + idx, err := state_accessors.ReadValidatorIndexByPublicKey(tx, b48) + if err != nil { + return nil, beaconhttp.NewEndpointError(http.StatusInternalServerError, err.Error()) + } + filterIndicies = append(filterIndicies, idx) + } else { + idx, err := strconv.ParseUint(id, 10, 64) + if err != nil { + return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) + } + filterIndicies = append(filterIndicies, idx) + } + } + // Check the filters' validity + for _, status := range statusFilters { + if err := checkValidStatus(status); err != nil { + return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error()) + } + } + _ = filterIndicies + _ = blockRoot + panic("implement me") + // time to begin motherfucker + // if blockId.head() { // Lets see if we point to head, if yes then we need to look at the head state we always keep. + // } + // return nil, nil +} diff --git a/cl/beacon/synced_data/synced_data.go b/cl/beacon/synced_data/synced_data.go index abc04251670..c8de023f888 100644 --- a/cl/beacon/synced_data/synced_data.go +++ b/cl/beacon/synced_data/synced_data.go @@ -28,18 +28,15 @@ func (s *SyncedDataManager) OnHeadState(newState *state.CachingBeaconState) (err if !s.enabled { return } - // Schedule update. - go func() { - s.mu.Lock() - defer s.mu.Unlock() - if s.headState == nil { - s.headState, err = newState.Copy() - } - err = newState.CopyInto(s.headState) - if err != nil { - log.Error("failed to copy head state", "err", err) - } - }() + s.mu.Lock() + defer s.mu.Unlock() + if s.headState == nil { + s.headState, err = newState.Copy() + } + err = newState.CopyInto(s.headState) + if err != nil { + log.Error("failed to copy head state", "err", err) + } return } diff --git a/cl/persistence/state/historical_states_reader/historical_states_reader.go b/cl/persistence/state/historical_states_reader/historical_states_reader.go index 611bb1d1a63..60abae12583 100644 --- a/cl/persistence/state/historical_states_reader/historical_states_reader.go +++ b/cl/persistence/state/historical_states_reader/historical_states_reader.go @@ -191,7 +191,7 @@ func (r *HistoricalStatesReader) ReadHistoricalState(ctx context.Context, tx kv. } if ret.Version() < clparams.AltairVersion { - return ret, ret.InitBeaconState() + return ret, nil } inactivityScores := solid.NewUint64ListSSZ(int(r.cfg.ValidatorRegistryLimit)) // Inactivity @@ -222,7 +222,7 @@ func (r *HistoricalStatesReader) ReadHistoricalState(ctx context.Context, tx kv. ret.SetNextSyncCommittee(nextSyncCommittee) // Execution if ret.Version() < clparams.BellatrixVersion { - return ret, ret.InitBeaconState() + return ret, nil } payloadHeader, err := block.Block.Body.ExecutionPayload.PayloadHeader() if err != nil { @@ -230,7 +230,7 @@ func (r *HistoricalStatesReader) ReadHistoricalState(ctx context.Context, tx kv. } ret.SetLatestExecutionPayloadHeader(payloadHeader) if ret.Version() < clparams.CapellaVersion { - return ret, ret.InitBeaconState() + return ret, nil } // Withdrawals @@ -245,7 +245,7 @@ func (r *HistoricalStatesReader) ReadHistoricalState(ctx context.Context, tx kv. return nil, fmt.Errorf("failed to read historical summaries: %w", err) } ret.SetHistoricalSummaries(historicalSummaries) - return ret, ret.InitBeaconState() + return ret, nil } func (r *HistoricalStatesReader) readHistoryHashVector(tx kv.Tx, genesisVector solid.HashVectorSSZ, slot, size uint64, table string, out solid.HashVectorSSZ) (err error) { diff --git a/cl/persistence/state/historical_states_reader/historical_states_reader_test.go b/cl/persistence/state/historical_states_reader/historical_states_reader_test.go index c3e64b3ed11..ebbae5bb65a 100644 --- a/cl/persistence/state/historical_states_reader/historical_states_reader_test.go +++ b/cl/persistence/state/historical_states_reader/historical_states_reader_test.go @@ -21,12 +21,12 @@ import ( func runTest(t *testing.T, blocks []*cltypes.SignedBeaconBlock, preState, postState *state.CachingBeaconState) { db := memdb.NewTestDB(t) - reader, _ := tests.LoadChain(blocks, db, t) + reader, _ := tests.LoadChain(blocks, postState, db, t) ctx := context.Background() vt := state_accessors.NewStaticValidatorTable() f := afero.NewMemMapFs() - a := antiquary.NewAntiquary(ctx, preState, vt, &clparams.MainnetBeaconConfig, datadir.New("/tmp"), nil, db, nil, reader, nil, log.New(), true, f) + a := antiquary.NewAntiquary(ctx, preState, vt, &clparams.MainnetBeaconConfig, datadir.New("/tmp"), nil, db, nil, reader, nil, log.New(), true, true, f) require.NoError(t, a.IncrementBeaconState(ctx, blocks[len(blocks)-1].Block.Slot+33)) // Now lets test it against the reader tx, err := db.BeginRw(ctx) diff --git a/cl/persistence/state/state_accessors.go b/cl/persistence/state/state_accessors.go index 893b17c7fce..60bbfda059e 100644 --- a/cl/persistence/state/state_accessors.go +++ b/cl/persistence/state/state_accessors.go @@ -129,6 +129,9 @@ func ReadValidatorIndexByPublicKey(tx kv.Tx, key libcommon.Bytes48) (uint64, err if index, err = tx.GetOne(kv.InvertedValidatorPublicKeys, key[:]); err != nil { return 0, err } + if len(index) == 0 { + return 0, nil + } return base_encoding.Decode64FromBytes4(index), nil } diff --git a/cl/phase1/forkchoice/forkchoice_mock.go b/cl/phase1/forkchoice/forkchoice_mock.go new file mode 100644 index 00000000000..c5415c27846 --- /dev/null +++ b/cl/phase1/forkchoice/forkchoice_mock.go @@ -0,0 +1,174 @@ +package forkchoice + +import ( + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/phase1/execution_client" +) + +// type ForkChoiceStorage interface { +// ForkChoiceStorageWriter +// ForkChoiceStorageReader +// } + +// type ForkChoiceStorageReader interface { +// Ancestor(root common.Hash, slot uint64) common.Hash +// AnchorSlot() uint64 +// Engine() execution_client.ExecutionEngine +// FinalizedCheckpoint() solid.Checkpoint +// FinalizedSlot() uint64 +// GetEth1Hash(eth2Root common.Hash) common.Hash +// GetHead() (common.Hash, uint64, error) +// HighestSeen() uint64 +// JustifiedCheckpoint() solid.Checkpoint +// JustifiedSlot() uint64 +// ProposerBoostRoot() common.Hash +// GetStateAtBlockRoot(blockRoot libcommon.Hash, alwaysCopy bool) (*state.CachingBeaconState, error) +// GetFinalityCheckpoints(blockRoot libcommon.Hash) (bool, solid.Checkpoint, solid.Checkpoint, solid.Checkpoint) +// GetSyncCommittees(blockRoot libcommon.Hash) (*solid.SyncCommittee, *solid.SyncCommittee, bool) +// Slot() uint64 +// Time() uint64 + +// GetStateAtSlot(slot uint64, alwaysCopy bool) (*state.CachingBeaconState, error) +// GetStateAtStateRoot(root libcommon.Hash, alwaysCopy bool) (*state.CachingBeaconState, error) +// } + +// type ForkChoiceStorageWriter interface { +// OnAttestation(attestation *solid.Attestation, fromBlock bool) error +// OnAttesterSlashing(attesterSlashing *cltypes.AttesterSlashing, test bool) error +// OnBlock(block *cltypes.SignedBeaconBlock, newPayload bool, fullValidation bool) error +// OnTick(time uint64) +// } + +// Make mocks with maps and simple setters and getters, panic on methods from ForkChoiceStorageWriter + +type ForkChoiceStorageMock struct { + Ancestors map[uint64]common.Hash + AnchorSlotVal uint64 + FinalizedCheckpointVal solid.Checkpoint + FinalizedSlotVal uint64 + HeadVal common.Hash + HeadSlotVal uint64 + HighestSeenVal uint64 + JustifiedCheckpointVal solid.Checkpoint + JustifiedSlotVal uint64 + ProposerBoostRootVal common.Hash + SlotVal uint64 + TimeVal uint64 + + StateAtBlockRootVal map[common.Hash]*state.CachingBeaconState + StateAtSlotVal map[uint64]*state.CachingBeaconState + GetSyncCommitteesVal map[common.Hash][2]*solid.SyncCommittee + GetFinalityCheckpointsVal map[common.Hash][3]solid.Checkpoint +} + +func NewForkChoiceStorageMock() *ForkChoiceStorageMock { + return &ForkChoiceStorageMock{ + Ancestors: make(map[uint64]common.Hash), + AnchorSlotVal: 0, + FinalizedCheckpointVal: solid.Checkpoint{}, + FinalizedSlotVal: 0, + HeadVal: common.Hash{}, + HighestSeenVal: 0, + JustifiedCheckpointVal: solid.Checkpoint{}, + JustifiedSlotVal: 0, + ProposerBoostRootVal: common.Hash{}, + SlotVal: 0, + TimeVal: 0, + StateAtBlockRootVal: make(map[common.Hash]*state.CachingBeaconState), + StateAtSlotVal: make(map[uint64]*state.CachingBeaconState), + GetSyncCommitteesVal: make(map[common.Hash][2]*solid.SyncCommittee), + GetFinalityCheckpointsVal: make(map[common.Hash][3]solid.Checkpoint), + } +} + +func (f *ForkChoiceStorageMock) Ancestor(root common.Hash, slot uint64) common.Hash { + return f.Ancestors[slot] +} + +func (f *ForkChoiceStorageMock) AnchorSlot() uint64 { + return f.AnchorSlotVal +} + +func (f *ForkChoiceStorageMock) Engine() execution_client.ExecutionEngine { + panic("implement me") +} + +func (f *ForkChoiceStorageMock) FinalizedCheckpoint() solid.Checkpoint { + return f.FinalizedCheckpointVal +} + +func (f *ForkChoiceStorageMock) FinalizedSlot() uint64 { + return f.FinalizedSlotVal +} + +func (f *ForkChoiceStorageMock) GetEth1Hash(eth2Root common.Hash) common.Hash { + panic("implement me") +} + +func (f *ForkChoiceStorageMock) GetHead() (common.Hash, uint64, error) { + return f.HeadVal, f.HeadSlotVal, nil +} + +func (f *ForkChoiceStorageMock) HighestSeen() uint64 { + return f.HighestSeenVal +} + +func (f *ForkChoiceStorageMock) JustifiedCheckpoint() solid.Checkpoint { + return f.JustifiedCheckpointVal +} + +func (f *ForkChoiceStorageMock) JustifiedSlot() uint64 { + return f.JustifiedSlotVal +} + +func (f *ForkChoiceStorageMock) ProposerBoostRoot() common.Hash { + return f.ProposerBoostRootVal +} + +func (f *ForkChoiceStorageMock) GetStateAtBlockRoot(blockRoot common.Hash, alwaysCopy bool) (*state.CachingBeaconState, error) { + return f.StateAtBlockRootVal[blockRoot], nil +} + +func (f *ForkChoiceStorageMock) GetFinalityCheckpoints(blockRoot common.Hash) (bool, solid.Checkpoint, solid.Checkpoint, solid.Checkpoint) { + oneNil := f.GetFinalityCheckpointsVal[blockRoot][0] != nil && f.GetFinalityCheckpointsVal[blockRoot][1] != nil && f.GetFinalityCheckpointsVal[blockRoot][2] != nil + return oneNil, f.GetFinalityCheckpointsVal[blockRoot][0], f.GetFinalityCheckpointsVal[blockRoot][1], f.GetFinalityCheckpointsVal[blockRoot][2] +} + +func (f *ForkChoiceStorageMock) GetSyncCommittees(blockRoot common.Hash) (*solid.SyncCommittee, *solid.SyncCommittee, bool) { + return f.GetSyncCommitteesVal[blockRoot][0], f.GetSyncCommitteesVal[blockRoot][1], f.GetSyncCommitteesVal[blockRoot][0] != nil && f.GetSyncCommitteesVal[blockRoot][1] != nil +} + +func (f *ForkChoiceStorageMock) GetStateAtSlot(slot uint64, alwaysCopy bool) (*state.CachingBeaconState, error) { + return f.StateAtSlotVal[slot], nil +} + +func (f *ForkChoiceStorageMock) Slot() uint64 { + return f.SlotVal +} + +func (f *ForkChoiceStorageMock) Time() uint64 { + return f.TimeVal +} + +func (f *ForkChoiceStorageMock) OnAttestation(attestation *solid.Attestation, fromBlock bool) error { + panic("implement me") +} + +func (f *ForkChoiceStorageMock) OnAttesterSlashing(attesterSlashing *cltypes.AttesterSlashing, test bool) error { + panic("implement me") +} + +func (f *ForkChoiceStorageMock) OnBlock(block *cltypes.SignedBeaconBlock, newPayload bool, fullValidation bool) error { + panic("implement me") +} + +func (f *ForkChoiceStorageMock) OnTick(time uint64) { + panic("implement me") +} + +func (f *ForkChoiceStorageMock) GetStateAtStateRoot(root common.Hash, alwaysCopy bool) (*state.CachingBeaconState, error) { + panic("implement me") +} diff --git a/cl/sentinel/handlers/handlers.go b/cl/sentinel/handlers/handlers.go index 87be5480c40..a97bfb57cd0 100644 --- a/cl/sentinel/handlers/handlers.go +++ b/cl/sentinel/handlers/handlers.go @@ -16,7 +16,7 @@ package handlers import ( "context" "errors" - "fmt" + "math" "strings" "sync" "time" @@ -47,7 +47,7 @@ type RateLimits struct { } const punishmentPeriod = time.Minute -const defaultRateLimit = 5000 +const defaultRateLimit = math.MaxInt const defaultBlockHandlerRateLimit = 200 var rateLimits = RateLimits{ @@ -163,7 +163,6 @@ func (c *ConsensusHandlers) wrapStreamHandler(name string, fn func(s network.Str err = fn(s) if err != nil { l["err"] = err - fmt.Println("err", err) log.Trace("[pubsubhandler] stream handler", l) // TODO: maybe we should log this _ = s.Reset() diff --git a/cl/sentinel/sentinel_gossip_test.go b/cl/sentinel/sentinel_gossip_test.go index 448c3513555..281f296afca 100644 --- a/cl/sentinel/sentinel_gossip_test.go +++ b/cl/sentinel/sentinel_gossip_test.go @@ -13,77 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestSentinelGossipAverage(t *testing.T) { - t.Skip("TODO: fix me") - listenAddrHost := "127.0.0.1" - - ctx := context.Background() - db, _, f, _, _ := loadChain(t) - raw := persistence.NewAferoRawBlockSaver(f, &clparams.MainnetBeaconConfig) - genesisConfig, networkConfig, beaconConfig := clparams.GetConfigsByNetwork(clparams.MainnetNetwork) - sentinel1, err := New(ctx, &SentinelConfig{ - NetworkConfig: networkConfig, - BeaconConfig: beaconConfig, - GenesisConfig: genesisConfig, - IpAddr: listenAddrHost, - Port: 7070, - EnableBlocks: true, - }, raw, db, log.New()) - require.NoError(t, err) - defer sentinel1.Stop() - - require.NoError(t, sentinel1.Start()) - h := sentinel1.host - - sentinel2, err := New(ctx, &SentinelConfig{ - NetworkConfig: networkConfig, - BeaconConfig: beaconConfig, - GenesisConfig: genesisConfig, - IpAddr: listenAddrHost, - Port: 7077, - EnableBlocks: true, - TCPPort: 9123, - }, raw, db, log.New()) - require.NoError(t, err) - defer sentinel2.Stop() - - require.NoError(t, sentinel2.Start()) - h2 := sentinel2.host - - sub1, err := sentinel1.SubscribeGossip(BeaconBlockSsz) - require.NoError(t, err) - defer sub1.Close() - - require.NoError(t, sub1.Listen()) - - sub2, err := sentinel2.SubscribeGossip(BeaconBlockSsz) - require.NoError(t, err) - defer sub2.Close() - require.NoError(t, sub2.Listen()) - - err = h.Connect(ctx, peer.AddrInfo{ - ID: h2.ID(), - Addrs: h2.Addrs(), - }) - require.NoError(t, err) - - ch := sentinel2.RecvGossip() - msg := []byte("hello") - go func() { - // delay to make sure that the connection is established - time.Sleep(5 * time.Second) - sub1.Publish(msg) - }() - - select { - case ans := <-ch: - require.Equal(t, len(msg), len(ans.Message.Data)) - require.Equal(t, msg, ans.Data) - case <-ctx.Done(): - t.Fatal("timeout") - } -} - func TestSentinelGossipOnHardFork(t *testing.T) { listenAddrHost := "127.0.0.1" @@ -149,14 +78,13 @@ func TestSentinelGossipOnHardFork(t *testing.T) { msg := []byte("hello") go func() { // delay to make sure that the connection is established - time.Sleep(time.Second) + time.Sleep(5 * time.Second) sub1.Publish(msg) }() previousTopic := "" select { case ans := <-ch: - require.Equal(t, len(msg), len(ans.Message.Data)) previousTopic = *ans.Topic case <-ctx.Done(): t.Fatal("timeout") @@ -167,7 +95,7 @@ func TestSentinelGossipOnHardFork(t *testing.T) { msg = []byte("hello1") go func() { // delay to make sure that the connection is established - time.Sleep(time.Second) + time.Sleep(5 * time.Second) sub1 = sentinel1.subManager.GetMatchingSubscription(string(BeaconBlockSsz.Name)) sub1.Publish(msg) }() diff --git a/cl/sentinel/sentinel_requests_test.go b/cl/sentinel/sentinel_requests_test.go index 34a58ab917a..f1ba704fdae 100644 --- a/cl/sentinel/sentinel_requests_test.go +++ b/cl/sentinel/sentinel_requests_test.go @@ -35,11 +35,11 @@ func loadChain(t *testing.T) (db kv.RwDB, blocks []*cltypes.SignedBeaconBlock, f blocks, preState, postState = tests.GetPhase0Random() db = memdb.NewTestDB(t) var reader *tests.MockBlockReader - reader, f = tests.LoadChain(blocks, db, t) + reader, f = tests.LoadChain(blocks, postState, db, t) ctx := context.Background() vt := state_accessors.NewStaticValidatorTable() - a := antiquary.NewAntiquary(ctx, preState, vt, &clparams.MainnetBeaconConfig, datadir.New("/tmp"), nil, db, nil, reader, nil, log.New(), true, f) + a := antiquary.NewAntiquary(ctx, preState, vt, &clparams.MainnetBeaconConfig, datadir.New("/tmp"), nil, db, nil, reader, nil, log.New(), true, true, f) require.NoError(t, a.IncrementBeaconState(ctx, blocks[len(blocks)-1].Block.Slot+33)) return } diff --git a/cmd/capcli/cli.go b/cmd/capcli/cli.go index e0636217661..53f9faceb7b 100644 --- a/cmd/capcli/cli.go +++ b/cmd/capcli/cli.go @@ -437,7 +437,7 @@ func (c *Chain) Run(ctx *Context) error { } downloader := network.NewBackwardBeaconDownloader(ctx, beacon, db) - cfg := stages.StageHistoryReconstruction(downloader, antiquary.NewAntiquary(ctx, nil, nil, nil, dirs, nil, nil, nil, nil, nil, nil, false, nil), csn, beaconDB, db, nil, genesisConfig, beaconConfig, true, true, bRoot, bs.Slot(), "/tmp", 300*time.Millisecond, log.Root()) + cfg := stages.StageHistoryReconstruction(downloader, antiquary.NewAntiquary(ctx, nil, nil, nil, dirs, nil, nil, nil, nil, nil, nil, false, false, nil), csn, beaconDB, db, nil, genesisConfig, beaconConfig, true, true, bRoot, bs.Slot(), "/tmp", 300*time.Millisecond, log.Root()) return stages.SpawnStageHistoryDownload(cfg, ctx, log.Root()) } @@ -838,6 +838,16 @@ func (r *RetrieveHistoricalState) Run(ctx *Context) error { if err != nil { return err } + endTime := time.Since(start) + hRoot, err := haveState.HashSSZ() + if err != nil { + return err + } + log.Info("Got state", "slot", haveState.Slot(), "root", libcommon.Hash(hRoot), "elapsed", endTime) + + if err := haveState.InitBeaconState(); err != nil { + return err + } v := haveState.Version() // encode and decode the state @@ -849,12 +859,10 @@ func (r *RetrieveHistoricalState) Run(ctx *Context) error { if err := haveState.DecodeSSZ(enc, int(v)); err != nil { return err } - endTime := time.Since(start) - hRoot, err := haveState.HashSSZ() + hRoot, err = haveState.HashSSZ() if err != nil { return err } - log.Info("Got state", "slot", haveState.Slot(), "root", libcommon.Hash(hRoot), "elapsed", endTime) if r.CompareFile == "" { return nil } diff --git a/cmd/caplin/caplin1/run.go b/cmd/caplin/caplin1/run.go index 47d1bd123d5..9a2f7a63230 100644 --- a/cmd/caplin/caplin1/run.go +++ b/cmd/caplin/caplin1/run.go @@ -197,7 +197,7 @@ func RunCaplinPhase1(ctx context.Context, sentinel sentinel.SentinelClient, engi if err != nil { return err } - antiq := antiquary.NewAntiquary(ctx, genesisState, vTables, beaconConfig, dirs, snDownloader, indexDB, csn, rcsn, historyDB, logger, states, af) + antiq := antiquary.NewAntiquary(ctx, genesisState, vTables, beaconConfig, dirs, snDownloader, indexDB, csn, rcsn, historyDB, logger, states, backfilling, af) // Create the antiquary go func() { if err := antiq.Loop(); err != nil {