diff --git a/beacon-chain/blockchain/process_attestation_test.go b/beacon-chain/blockchain/process_attestation_test.go index 22da74147012..bf0aa6f6d38c 100644 --- a/beacon-chain/blockchain/process_attestation_test.go +++ b/beacon-chain/blockchain/process_attestation_test.go @@ -114,7 +114,7 @@ func TestStore_OnAttestation(t *testing.T) { a: ðpb.Attestation{Data: ðpb.AttestationData{Target: ðpb.Checkpoint{Root: BlkWithOutStateRoot[:]}}}, s: &pb.BeaconState{}, wantErr: true, - wantErrString: "could not get pre state for slot 0: could not get state summary", + wantErrString: "could not get pre state for slot 0: could not get ancestor state", }, { name: "process attestation doesn't match current epoch", diff --git a/beacon-chain/state/stategen/hot.go b/beacon-chain/state/stategen/hot.go index 267d338c93dc..4f561e0d6805 100644 --- a/beacon-chain/state/stategen/hot.go +++ b/beacon-chain/state/stategen/hot.go @@ -72,16 +72,9 @@ func (s *State) loadHotStateByRoot(ctx context.Context, blockRoot [32]byte) (*st // Since the hot state is not in cache nor DB, start replaying using the parent state which is // retrieved using input block's parent root. - blk, err := s.beaconDB.Block(ctx, blockRoot) + startState, err := s.lastAncestorState(ctx, blockRoot) if err != nil { - return nil, err - } - if blk == nil { - return nil, errUnknownBlock - } - startState, err := s.loadHotStateByRoot(ctx, bytesutil.ToBytes32(blk.Block.ParentRoot)) - if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not get ancestor state") } if startState == nil { return nil, errUnknownBoundaryState @@ -139,3 +132,37 @@ func (s *State) loadHotStateBySlot(ctx context.Context, slot uint64) (*state.Bea return s.ReplayBlocks(ctx, startState, replayBlks, slot) } + +// This returns the last saved in DB ancestor state of the input block root. +// It recursively look up block's parent until a corresponding state of the block root +// is found in the DB. +func (s *State) lastAncestorState(ctx context.Context, root [32]byte) (*state.BeaconState, error) { + ctx, span := trace.StartSpan(ctx, "stateGen.lastAncestorState") + defer span.End() + + b, err := s.beaconDB.Block(ctx, root) + if err != nil { + return nil, err + } + if b == nil { + return nil, errUnknownBlock + } + + for { + if ctx.Err() != nil { + return nil, ctx.Err() + } + parentRoot := bytesutil.ToBytes32(b.Block.ParentRoot) + if s.beaconDB.HasState(ctx, parentRoot) { + return s.beaconDB.State(ctx, parentRoot) + } + + b, err = s.beaconDB.Block(ctx, parentRoot) + if err != nil { + return nil, err + } + if b == nil { + return nil, errUnknownBlock + } + } +} diff --git a/beacon-chain/state/stategen/hot_test.go b/beacon-chain/state/stategen/hot_test.go index faffd3c4d390..ecf2452c6f59 100644 --- a/beacon-chain/state/stategen/hot_test.go +++ b/beacon-chain/state/stategen/hot_test.go @@ -245,3 +245,60 @@ func TestLoadHoteStateBySlot_CanAdvanceSlotUsingDB(t *testing.T) { t.Error("Did not correctly load state") } } + +func TestLastAncestorState_CanGet(t *testing.T) { + ctx := context.Background() + db := testDB.SetupDB(t) + defer testDB.TeardownDB(t, db) + service := New(db, cache.NewStateSummaryCache()) + + b0 := ðpb.BeaconBlock{Slot: 0, ParentRoot: []byte{'a'}} + r0, err := ssz.HashTreeRoot(b0) + if err != nil { + t.Fatal(err) + } + b1 := ðpb.BeaconBlock{Slot: 1, ParentRoot: r0[:]} + r1, err := ssz.HashTreeRoot(b1) + if err != nil { + t.Fatal(err) + } + b2 := ðpb.BeaconBlock{Slot: 2, ParentRoot: r1[:]} + r2, err := ssz.HashTreeRoot(b2) + if err != nil { + t.Fatal(err) + } + b3 := ðpb.BeaconBlock{Slot: 3, ParentRoot: r2[:]} + r3, err := ssz.HashTreeRoot(b3) + if err != nil { + t.Fatal(err) + } + + b1State := testutil.NewBeaconState() + if err := b1State.SetSlot(1); err != nil { + t.Fatal(err) + } + + if err := service.beaconDB.SaveBlock(ctx, ðpb.SignedBeaconBlock{Block: b0}); err != nil { + t.Fatal(err) + } + if err := service.beaconDB.SaveBlock(ctx, ðpb.SignedBeaconBlock{Block: b1}); err != nil { + t.Fatal(err) + } + if err := service.beaconDB.SaveBlock(ctx, ðpb.SignedBeaconBlock{Block: b2}); err != nil { + t.Fatal(err) + } + if err := service.beaconDB.SaveBlock(ctx, ðpb.SignedBeaconBlock{Block: b3}); err != nil { + t.Fatal(err) + } + if err := service.beaconDB.SaveState(ctx, b1State, r1); err != nil { + t.Fatal(err) + } + + lastState, err := service.lastAncestorState(ctx, r3) + if err != nil { + t.Fatal(err) + } + if lastState.Slot() != b1State.Slot() { + t.Error("Did not get wanted state") + } +}