From 25c13663d204651c2b5775acd091e5459b08f5ca Mon Sep 17 00:00:00 2001 From: terence tsao Date: Mon, 9 Mar 2020 17:22:45 +0100 Subject: [PATCH] Add hot state by slot retrival (#5052) * Update replay conditions * loadHotStateBySlot * Tests and gaz * Tests --- beacon-chain/state/stategen/BUILD.bazel | 1 + beacon-chain/state/stategen/hot.go | 43 ++++++++++++++++++++++ beacon-chain/state/stategen/hot_test.go | 42 +++++++++++++++++++++ beacon-chain/state/stategen/replay.go | 7 +++- beacon-chain/state/stategen/replay_test.go | 22 +++++++---- 5 files changed, 105 insertions(+), 10 deletions(-) diff --git a/beacon-chain/state/stategen/BUILD.bazel b/beacon-chain/state/stategen/BUILD.bazel index 4527fec36..ae1537be0 100644 --- a/beacon-chain/state/stategen/BUILD.bazel +++ b/beacon-chain/state/stategen/BUILD.bazel @@ -14,6 +14,7 @@ go_library( visibility = ["//beacon-chain:__subpackages__"], deps = [ "//beacon-chain/cache:go_default_library", + "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/state:go_default_library", "//beacon-chain/db:go_default_library", "//beacon-chain/db/filters:go_default_library", diff --git a/beacon-chain/state/stategen/hot.go b/beacon-chain/state/stategen/hot.go index 1d80bb095..1a47727e9 100644 --- a/beacon-chain/state/stategen/hot.go +++ b/beacon-chain/state/stategen/hot.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/shared/bytesutil" "go.opencensus.io/trace" @@ -59,3 +60,45 @@ func (s *State) loadHotStateByRoot(ctx context.Context, blockRoot [32]byte) (*st return hotState, nil } + +// This loads a hot state by slot where the slot lies between the epoch boundary points. +// This is a slower implementation (versus ByRoot) as slot is the only argument. It require fetching +// all the blocks between the epoch boundary points for playback. +// Use `loadHotStateByRoot` unless you really don't know the root. +func (s *State) loadHotStateBySlot(ctx context.Context, slot uint64) (*state.BeaconState, error) { + ctx, span := trace.StartSpan(ctx, "stateGen.loadHotStateBySlot") + defer span.End() + + // Gather epoch boundary information, that is where node starts to replay the blocks. + boundarySlot := helpers.StartSlot(helpers.SlotToEpoch(slot)) + boundaryRoot, ok := s.epochBoundaryRoot(boundarySlot) + if !ok { + return nil, errUnknownBoundaryRoot + } + // Try the cache first then try the DB. + boundaryState := s.hotStateCache.Get(boundaryRoot) + var err error + if boundaryState == nil { + boundaryState, err = s.beaconDB.State(ctx, boundaryRoot) + if err != nil { + return nil, err + } + if boundaryState == nil { + return nil, errUnknownBoundaryState + } + } + + // Gather the last saved block root and the slot number. + lastValidRoot, lastValidSlot, err := s.lastSavedBlock(ctx, slot) + if err != nil { + return nil, errors.Wrap(err, "could not get last valid block for hot state using slot") + } + + // Load and replay blocks to get the intermediate state. + replayBlks, err := s.LoadBlocks(ctx, boundaryState.Slot()+1, lastValidSlot, lastValidRoot) + if err != nil { + return nil, err + } + + return s.ReplayBlocks(ctx, boundaryState, replayBlks, slot) +} diff --git a/beacon-chain/state/stategen/hot_test.go b/beacon-chain/state/stategen/hot_test.go index a808c6eb0..44405b69c 100644 --- a/beacon-chain/state/stategen/hot_test.go +++ b/beacon-chain/state/stategen/hot_test.go @@ -94,3 +94,45 @@ func TestLoadHoteStateByRoot_FromDBBoundaryCase(t *testing.T) { t.Error("Did not correctly load state") } } + +func TestLoadHoteStateBySlot_CanAdvanceSlotUsingCache(t *testing.T) { + ctx := context.Background() + db := testDB.SetupDB(t) + defer testDB.TeardownDB(t, db) + service := New(db) + beaconState, _ := testutil.DeterministicGenesisState(t, 32) + r := [32]byte{'A'} + service.hotStateCache.Put(r, beaconState) + service.setEpochBoundaryRoot(0, r) + + slot := uint64(10) + loadedState, err := service.loadHotStateBySlot(ctx, slot) + if err != nil { + t.Fatal(err) + } + if loadedState.Slot() != slot { + t.Error("Did not correctly load state") + } +} + +func TestLoadHoteStateBySlot_CanAdvanceSlotUsingDB(t *testing.T) { + ctx := context.Background() + db := testDB.SetupDB(t) + defer testDB.TeardownDB(t, db) + service := New(db) + beaconState, _ := testutil.DeterministicGenesisState(t, 32) + r := [32]byte{'A'} + service.setEpochBoundaryRoot(0, r) + if err := service.beaconDB.SaveState(ctx, beaconState, r); err != nil { + t.Fatal(err) + } + + slot := uint64(10) + loadedState, err := service.loadHotStateBySlot(ctx, slot) + if err != nil { + t.Fatal(err) + } + if loadedState.Slot() != slot { + t.Error("Did not correctly load state") + } +} diff --git a/beacon-chain/state/stategen/replay.go b/beacon-chain/state/stategen/replay.go index b335d5a60..be5a202bf 100644 --- a/beacon-chain/state/stategen/replay.go +++ b/beacon-chain/state/stategen/replay.go @@ -13,6 +13,7 @@ import ( stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/featureconfig" + "github.com/prysmaticlabs/prysm/shared/params" "go.opencensus.io/trace" ) @@ -247,7 +248,8 @@ func (s *State) lastSavedBlock(ctx context.Context, slot uint64) ([32]byte, uint return [32]byte{}, 0, err } if len(rs) == 0 { - return [32]byte{}, 0, errors.New("block root has 0 length") + // Return zero hash if there hasn't been any block in the DB yet. + return params.BeaconChainConfig{}.ZeroHash, 0, nil } lastRoot := rs[len(rs)-1] @@ -286,7 +288,8 @@ func (s *State) lastSavedState(ctx context.Context, slot uint64) ([32]byte, erro return [32]byte{}, err } if len(rs) == 0 { - return [32]byte{}, errors.New("block root has 0 length") + // Return zero hash if there hasn't been any block in the DB yet. + return params.BeaconChainConfig{}.ZeroHash, nil } for i := len(rs) - 1; i >= 0; i-- { // Stop until a state is saved. diff --git a/beacon-chain/state/stategen/replay_test.go b/beacon-chain/state/stategen/replay_test.go index b9c36ffd9..b1434c21c 100644 --- a/beacon-chain/state/stategen/replay_test.go +++ b/beacon-chain/state/stategen/replay_test.go @@ -415,7 +415,7 @@ func TestLastSavedBlock_CanGet(t *testing.T) { } } -func TestLastSavedBlock_OutOfRange(t *testing.T) { +func TestLastSavedBlock_NoSavedBlock(t *testing.T) { db := testDB.SetupDB(t) defer testDB.TeardownDB(t, db) ctx := context.Background() @@ -429,9 +429,12 @@ func TestLastSavedBlock_OutOfRange(t *testing.T) { t.Fatal(err) } - _, _, err := s.lastSavedBlock(ctx, s.lastArchivedSlot+1) - if err.Error() != "block root has 0 length" { - t.Error("Did not get wanted error") + r, slot, err := s.lastSavedBlock(ctx, s.lastArchivedSlot+1) + if err != nil { + t.Fatal(err) + } + if slot != 0 || r != params.BeaconConfig().ZeroHash { + t.Error("Did not get no saved block info") } } @@ -504,7 +507,7 @@ func TestLastSavedState_CanGet(t *testing.T) { } } -func TestLastSavedState_OutOfRange(t *testing.T) { +func TestLastSavedState_NoSavedBlockState(t *testing.T) { db := testDB.SetupDB(t) defer testDB.TeardownDB(t, db) ctx := context.Background() @@ -518,9 +521,12 @@ func TestLastSavedState_OutOfRange(t *testing.T) { t.Fatal(err) } - _, err := s.lastSavedState(ctx, s.lastArchivedSlot+1) - if err.Error() != "block root has 0 length" { - t.Error("Did not get wanted error") + r, err := s.lastSavedState(ctx, s.lastArchivedSlot+1) + if err != nil { + t.Fatal(err) + } + if r != params.BeaconConfig().ZeroHash { + t.Error("Did not get no saved block info") } }