Skip to content

Commit

Permalink
Better head object coupling for chain service (#4869)
Browse files Browse the repository at this point in the history
* Done
* Fixed lock
* Fixed all the tests
* Comments
* Fixed more tests
* Merge branch 'master' into better-head-obj
* Fixed more tests
* Merge branch 'better-head-obj' of git+ssh://github.com/prysmaticlabs/prysm into better-head-obj
* Prestons feedback & fixed test
* Nishant's feedback
* Participation edge case
* Gaz
* Merge branch 'master' into better-head-obj
* Merge branch 'master' of git+ssh://github.com/prysmaticlabs/prysm into better-head-obj
* Raul's feedback
* Merge branch 'better-head-obj' of git+ssh://github.com/prysmaticlabs/prysm into better-head-obj
  • Loading branch information
terencechain committed Feb 15, 2020
1 parent 92a9147 commit 456ac5f
Show file tree
Hide file tree
Showing 22 changed files with 232 additions and 244 deletions.
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)
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

0 comments on commit 456ac5f

Please sign in to comment.