Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better head object coupling for chain service #4869

Merged
merged 16 commits into from Feb 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion beacon-chain/blockchain/BUILD.bazel
Expand Up @@ -45,7 +45,6 @@ go_library(
"//shared/slotutil:go_default_library",
"//shared/traceutil:go_default_library",
"@com_github_emicklei_dot//:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
"@com_github_prysmaticlabs_go_ssz//:go_default_library",
Expand Down
76 changes: 32 additions & 44 deletions beacon-chain/blockchain/chain_info.go
Expand Up @@ -5,7 +5,6 @@ import (
"context"
"time"

"github.com/gogo/protobuf/proto"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/go-ssz"
"github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute"
Expand Down Expand Up @@ -60,67 +59,63 @@ type ParticipationFetcher interface {

// FinalizedCheckpt returns the latest finalized checkpoint from head state.
func (s *Service) FinalizedCheckpt() *ethpb.Checkpoint {
if s.headState == nil || s.headState.FinalizedCheckpoint() == nil {
if s.finalizedCheckpt == nil {
return &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
}
cpt := s.headState.FinalizedCheckpoint()

// If head state exists but there hasn't been a finalized check point,
// the check point's root should refer to genesis block root.
if bytes.Equal(cpt.Root, params.BeaconConfig().ZeroHash[:]) {
if bytes.Equal(s.finalizedCheckpt.Root, params.BeaconConfig().ZeroHash[:]) {
return &ethpb.Checkpoint{Root: s.genesisRoot[:]}
}
return cpt

return state.CopyCheckpoint(s.finalizedCheckpt)
}

// CurrentJustifiedCheckpt returns the current justified checkpoint from head state.
func (s *Service) CurrentJustifiedCheckpt() *ethpb.Checkpoint {
if s.headState == nil || s.headState.CurrentJustifiedCheckpoint() == nil {
if s.justifiedCheckpt == nil {
return &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
}

cpt := s.headState.CurrentJustifiedCheckpoint()
// If head state exists but there hasn't been a justified check point,
// the check point root should refer to genesis block root.
if bytes.Equal(cpt.Root, params.BeaconConfig().ZeroHash[:]) {
if bytes.Equal(s.justifiedCheckpt.Root, params.BeaconConfig().ZeroHash[:]) {
return &ethpb.Checkpoint{Root: s.genesisRoot[:]}
}

return cpt
return state.CopyCheckpoint(s.justifiedCheckpt)
}

// PreviousJustifiedCheckpt returns the previous justified checkpoint from head state.
func (s *Service) PreviousJustifiedCheckpt() *ethpb.Checkpoint {

if s.headState == nil || s.headState.PreviousJustifiedCheckpoint() == nil {
if s.prevJustifiedCheckpt == nil {
return &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
}

cpt := s.headState.PreviousJustifiedCheckpoint()
// If head state exists but there hasn't been a justified check point,
// the check point root should refer to genesis block root.
if bytes.Equal(cpt.Root, params.BeaconConfig().ZeroHash[:]) {
if bytes.Equal(s.prevJustifiedCheckpt.Root, params.BeaconConfig().ZeroHash[:]) {
return &ethpb.Checkpoint{Root: s.genesisRoot[:]}
}

return cpt
return state.CopyCheckpoint(s.prevJustifiedCheckpt)
}

// HeadSlot returns the slot of the head of the chain.
func (s *Service) HeadSlot() uint64 {
s.headLock.RLock()
defer s.headLock.RUnlock()
if !s.hasHeadState() {
return 0
}

return s.headSlot
return s.headSlot()
}

// HeadRoot returns the root of the head of the chain.
func (s *Service) HeadRoot(ctx context.Context) ([]byte, error) {
s.headLock.RLock()
defer s.headLock.RUnlock()

root := s.canonicalRoots[s.headSlot]
if len(root) != 0 {
return root, nil
if s.headRoot() != params.BeaconConfig().ZeroHash {
r := s.headRoot()
return r[:], nil
}

b, err := s.beaconDB.HeadBlock(ctx)
Expand All @@ -141,46 +136,39 @@ func (s *Service) HeadRoot(ctx context.Context) ([]byte, error) {

// HeadBlock returns the head block of the chain.
func (s *Service) HeadBlock() *ethpb.SignedBeaconBlock {
s.headLock.RLock()
defer s.headLock.RUnlock()

return proto.Clone(s.headBlock).(*ethpb.SignedBeaconBlock)
return s.headBlock()
}

// HeadState returns the head state of the chain.
// If the head state is nil from service struct,
// it will attempt to get from DB and error if nil again.
func (s *Service) HeadState(ctx context.Context) (*state.BeaconState, error) {
s.headLock.RLock()
defer s.headLock.RUnlock()

if s.headState == nil {
headState, err := s.beaconDB.HeadState(ctx)
if err != nil {
return nil, err
}
s.headState = headState
return headState, nil
if s.hasHeadState() {
return s.headState(), nil
}

return s.headState.Copy(), nil
headState, err := s.beaconDB.HeadState(ctx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update cache if we have a cache miss?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not here. That's what the PR aim to avoid. Update individual head field. If there's a miss setHead will be used during init

if err != nil {
return nil, err
}
return headState, nil
}

// HeadValidatorsIndices returns a list of active validator indices from the head view of a given epoch.
func (s *Service) HeadValidatorsIndices(epoch uint64) ([]uint64, error) {
if s.headState == nil {
if !s.hasHeadState() {
return []uint64{}, nil
}
return helpers.ActiveValidatorIndices(s.headState, epoch)
return helpers.ActiveValidatorIndices(s.headState(), epoch)
}

// HeadSeed returns the seed from the head view of a given epoch.
func (s *Service) HeadSeed(epoch uint64) ([32]byte, error) {
if s.headState == nil {
if !s.hasHeadState() {
return [32]byte{}, nil
}

return helpers.Seed(s.headState, epoch, params.BeaconConfig().DomainBeaconAttester)
return helpers.Seed(s.headState(), epoch, params.BeaconConfig().DomainBeaconAttester)
}

// GenesisTime returns the genesis time of beacon chain.
Expand All @@ -190,13 +178,13 @@ func (s *Service) GenesisTime() time.Time {

// CurrentFork retrieves the latest fork information of the beacon chain.
func (s *Service) CurrentFork() *pb.Fork {
if s.headState == nil {
if !s.hasHeadState() {
return &pb.Fork{
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
}
}
return s.headState.Fork()
return s.headState().Fork()
}

// Participation returns the participation stats of a given epoch.
Expand Down
15 changes: 7 additions & 8 deletions beacon-chain/blockchain/chain_info_norace_test.go
Expand Up @@ -4,15 +4,15 @@ import (
"context"
"testing"

ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
)

func TestHeadSlot_DataRace(t *testing.T) {
db := testDB.SetupDB(t)
defer testDB.TeardownDB(t, db)
s := &Service{
beaconDB: db,
canonicalRoots: make(map[uint64][]byte),
beaconDB: db,
}
go func() {
s.saveHead(
Expand All @@ -27,8 +27,8 @@ func TestHeadRoot_DataRace(t *testing.T) {
db := testDB.SetupDB(t)
defer testDB.TeardownDB(t, db)
s := &Service{
beaconDB: db,
canonicalRoots: make(map[uint64][]byte),
beaconDB: db,
head: &head{root: [32]byte{'A'}},
}
go func() {
s.saveHead(
Expand All @@ -45,8 +45,8 @@ func TestHeadBlock_DataRace(t *testing.T) {
db := testDB.SetupDB(t)
defer testDB.TeardownDB(t, db)
s := &Service{
beaconDB: db,
canonicalRoots: make(map[uint64][]byte),
beaconDB: db,
head: &head{block: &ethpb.SignedBeaconBlock{}},
}
go func() {
s.saveHead(
Expand All @@ -61,8 +61,7 @@ func TestHeadState_DataRace(t *testing.T) {
db := testDB.SetupDB(t)
defer testDB.TeardownDB(t, db)
s := &Service{
beaconDB: db,
canonicalRoots: make(map[uint64][]byte),
beaconDB: db,
}
go func() {
s.saveHead(
Expand Down
55 changes: 28 additions & 27 deletions beacon-chain/blockchain/chain_info_test.go
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/state"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/testutil"
)

// Ensure Service implements chain info interface.
Expand All @@ -25,7 +24,6 @@ func TestFinalizedCheckpt_Nil(t *testing.T) {
db := testDB.SetupDB(t)
defer testDB.TeardownDB(t, db)
c := setupBeaconChain(t, db)
c.headState, _ = testutil.DeterministicGenesisState(t, 1)
if !bytes.Equal(c.FinalizedCheckpt().Root, params.BeaconConfig().ZeroHash[:]) {
t.Error("Incorrect pre chain start value")
}
Expand All @@ -50,7 +48,7 @@ func TestFinalizedCheckpt_CanRetrieve(t *testing.T) {

cp := &ethpb.Checkpoint{Epoch: 5, Root: []byte("foo")}
c := setupBeaconChain(t, db)
c.headState, _ = state.InitializeFromProto(&pb.BeaconState{FinalizedCheckpoint: cp})
c.finalizedCheckpt = cp

if c.FinalizedCheckpt().Epoch != cp.Epoch {
t.Errorf("Finalized epoch at genesis should be %d, got: %d", cp.Epoch, c.FinalizedCheckpt().Epoch)
Expand All @@ -61,10 +59,11 @@ func TestFinalizedCheckpt_GenesisRootOk(t *testing.T) {
db := testDB.SetupDB(t)
defer testDB.TeardownDB(t, db)

cp := &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
genesisRoot := [32]byte{'A'}
cp := &ethpb.Checkpoint{Root: genesisRoot[:]}
c := setupBeaconChain(t, db)
c.headState, _ = state.InitializeFromProto(&pb.BeaconState{FinalizedCheckpoint: cp})
c.genesisRoot = [32]byte{'A'}
c.finalizedCheckpt = cp
c.genesisRoot = genesisRoot

if !bytes.Equal(c.FinalizedCheckpt().Root, c.genesisRoot[:]) {
t.Errorf("Got: %v, wanted: %v", c.FinalizedCheckpt().Root, c.genesisRoot[:])
Expand All @@ -77,7 +76,7 @@ func TestCurrentJustifiedCheckpt_CanRetrieve(t *testing.T) {

cp := &ethpb.Checkpoint{Epoch: 6, Root: []byte("foo")}
c := setupBeaconChain(t, db)
c.headState, _ = state.InitializeFromProto(&pb.BeaconState{CurrentJustifiedCheckpoint: cp})
c.justifiedCheckpt = cp

if c.CurrentJustifiedCheckpt().Epoch != cp.Epoch {
t.Errorf("Current Justifiied epoch at genesis should be %d, got: %d", cp.Epoch, c.CurrentJustifiedCheckpt().Epoch)
Expand All @@ -88,10 +87,11 @@ func TestJustifiedCheckpt_GenesisRootOk(t *testing.T) {
db := testDB.SetupDB(t)
defer testDB.TeardownDB(t, db)

cp := &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
genesisRoot := [32]byte{'B'}
cp := &ethpb.Checkpoint{Root: genesisRoot[:]}
c := setupBeaconChain(t, db)
c.headState, _ = state.InitializeFromProto(&pb.BeaconState{CurrentJustifiedCheckpoint: cp})
c.genesisRoot = [32]byte{'B'}
c.justifiedCheckpt = cp
c.genesisRoot = genesisRoot

if !bytes.Equal(c.CurrentJustifiedCheckpt().Root, c.genesisRoot[:]) {
t.Errorf("Got: %v, wanted: %v", c.CurrentJustifiedCheckpt().Root, c.genesisRoot[:])
Expand All @@ -104,7 +104,7 @@ func TestPreviousJustifiedCheckpt_CanRetrieve(t *testing.T) {

cp := &ethpb.Checkpoint{Epoch: 7, Root: []byte("foo")}
c := setupBeaconChain(t, db)
c.headState, _ = state.InitializeFromProto(&pb.BeaconState{PreviousJustifiedCheckpoint: cp})
c.prevJustifiedCheckpt = cp

if c.PreviousJustifiedCheckpt().Epoch != cp.Epoch {
t.Errorf("Previous Justifiied epoch at genesis should be %d, got: %d", cp.Epoch, c.PreviousJustifiedCheckpt().Epoch)
Expand All @@ -115,10 +115,11 @@ func TestPrevJustifiedCheckpt_GenesisRootOk(t *testing.T) {
db := testDB.SetupDB(t)
defer testDB.TeardownDB(t, db)

cp := &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
genesisRoot := [32]byte{'C'}
cp := &ethpb.Checkpoint{Root: genesisRoot[:]}
c := setupBeaconChain(t, db)
c.headState, _ = state.InitializeFromProto(&pb.BeaconState{PreviousJustifiedCheckpoint: cp})
c.genesisRoot = [32]byte{'C'}
c.prevJustifiedCheckpt = cp
c.genesisRoot = genesisRoot

if !bytes.Equal(c.PreviousJustifiedCheckpt().Root, c.genesisRoot[:]) {
t.Errorf("Got: %v, wanted: %v", c.PreviousJustifiedCheckpt().Root, c.genesisRoot[:])
Expand All @@ -127,28 +128,26 @@ func TestPrevJustifiedCheckpt_GenesisRootOk(t *testing.T) {

func TestHeadSlot_CanRetrieve(t *testing.T) {
c := &Service{}
c.headSlot = 100
s, _ := state.InitializeFromProto(&pb.BeaconState{})
c.head = &head{slot: 100, state: s}
if c.HeadSlot() != 100 {
t.Errorf("Wanted head slot: %d, got: %d", 100, c.HeadSlot())
}
}

func TestHeadRoot_CanRetrieve(t *testing.T) {
c := &Service{canonicalRoots: make(map[uint64][]byte)}
c.headSlot = 100
c.canonicalRoots[c.headSlot] = []byte{'A'}
headRoot, err := c.HeadRoot(context.Background())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal([]byte{'A'}, headRoot) {
t.Errorf("Wanted head root: %v, got: %d", []byte{'A'}, headRoot)
c := &Service{}
c.head = &head{root: [32]byte{'A'}}
if [32]byte{'A'} != c.headRoot() {
t.Errorf("Wanted head root: %v, got: %d", []byte{'A'}, c.headRoot())
}
}

func TestHeadBlock_CanRetrieve(t *testing.T) {
b := &ethpb.SignedBeaconBlock{Block: &ethpb.BeaconBlock{Slot: 1}}
c := &Service{headBlock: b}
c := &Service{}
c.head = &head{block: b}

if !reflect.DeepEqual(b, c.HeadBlock()) {
t.Error("incorrect head block received")
}
Expand All @@ -159,7 +158,8 @@ func TestHeadState_CanRetrieve(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c := &Service{headState: s}
c := &Service{}
c.head = &head{state: s}
headState, err := c.HeadState(context.Background())
if err != nil {
t.Fatal(err)
Expand All @@ -183,7 +183,8 @@ func TestCurrentFork_CanRetrieve(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c := &Service{headState: s}
c := &Service{}
c.head = &head{state: s}
if !proto.Equal(c.CurrentFork(), f) {
t.Error("Received incorrect fork version")
}
Expand Down