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

Blockchain service provides IsCanonical getter #5932

Merged
merged 9 commits into from
May 20, 2020
20 changes: 20 additions & 0 deletions beacon-chain/blockchain/chain_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ChainInfoFetcher interface {
HeadFetcher
FinalizationFetcher
GenesisFetcher
CanonicalFetcher
}

// TimeFetcher retrieves the Eth2 data that's related to time.
Expand Down Expand Up @@ -51,6 +52,11 @@ type ForkFetcher interface {
CurrentFork() *pb.Fork
}

// CanonicalFetcher retrieves the current chain's canonical information.
type CanonicalFetcher interface {
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
}

// FinalizationFetcher defines a common interface for methods in blockchain service which
// directly retrieves finalization and justification related data.
type FinalizationFetcher interface {
Expand Down Expand Up @@ -222,3 +228,17 @@ func (s *Service) Participation(epoch uint64) *precompute.Balance {

return s.epochParticipation[epoch]
}

// IsCanonical returns true if the input block root is part of the canonical chain.
func (s *Service) IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error) {
// If the block has been finalized, the block will always be part of the canonical chain.
if s.beaconDB.IsFinalizedBlock(ctx, blockRoot) {
return true, nil
}

// If the block has not been finalized, the block must be recent. Check recent canonical roots
// mapping which uses proto array fork choice.
s.recentCanonicalBlocksLock.RLock()
defer s.recentCanonicalBlocksLock.RUnlock()
return s.recentCanonicalBlocks[blockRoot], nil
}
30 changes: 30 additions & 0 deletions beacon-chain/blockchain/head.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
"github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
Expand Down Expand Up @@ -46,6 +47,10 @@ func (s *Service) updateHead(ctx context.Context, balances []uint64) error {
return err
}

if err := s.updateRecentCanonicalBlocks(ctx, headRoot); err != nil {
return err
}

// Save head to the local service cache.
return s.saveHead(ctx, headRoot)
}
Expand Down Expand Up @@ -254,3 +259,28 @@ func (s *Service) hasHeadState() bool {

return s.head != nil && s.head.state != nil
}

// This updates recent canonical block mapping. It uses input head root and retrieves
// all the canonical block roots that are ancestor of the input head block root.
func (s *Service) updateRecentCanonicalBlocks(ctx context.Context, headRoot [32]byte) error {
s.recentCanonicalBlocksLock.Lock()
defer s.recentCanonicalBlocksLock.Unlock()

s.recentCanonicalBlocks = make(map[[32]byte]bool)
s.recentCanonicalBlocks[headRoot] = true
nodes := s.forkChoiceStore.Nodes()
node := s.forkChoiceStore.Node(headRoot)
if node == nil {
return nil
}

for node.Parent != protoarray.NonExistentNode {
if ctx.Err() != nil {
return ctx.Err()
}
node = nodes[node.Parent]
s.recentCanonicalBlocks[node.Root] = true
}

return nil
}
59 changes: 59 additions & 0 deletions beacon-chain/blockchain/head_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,62 @@ func TestSaveHead_Different_Reorg(t *testing.T) {
}
testutil.AssertLogsContain(t, hook, "Chain reorg occurred")
}

func TestUpdateRecentCanonicalBlocks_CanUpdateWithoutParent(t *testing.T) {
db := testDB.SetupDB(t)
service := setupBeaconChain(t, db)

r := [32]byte{'a'}
if err := service.updateRecentCanonicalBlocks(context.Background(), r); err != nil {
t.Fatal(err)
}
canonical, err := service.IsCanonical(context.Background(), r)
if err != nil {
t.Fatal(err)
}
if !canonical {
t.Error("Block should be canonical")
}
}

func TestUpdateRecentCanonicalBlocks_CanUpdateWithParent(t *testing.T) {
db := testDB.SetupDB(t)
service := setupBeaconChain(t, db)
oldHead := [32]byte{'a'}
if err := service.forkChoiceStore.ProcessBlock(context.Background(), 1, oldHead, [32]byte{'g'}, [32]byte{}, 0, 0); err != nil {
t.Fatal(err)
}
currentHead := [32]byte{'b'}
if err := service.forkChoiceStore.ProcessBlock(context.Background(), 3, currentHead, oldHead, [32]byte{}, 0, 0); err != nil {
t.Fatal(err)
}
forkedRoot := [32]byte{'c'}
if err := service.forkChoiceStore.ProcessBlock(context.Background(), 2, forkedRoot, oldHead, [32]byte{}, 0, 0); err != nil {
t.Fatal(err)
}

if err := service.updateRecentCanonicalBlocks(context.Background(), currentHead); err != nil {
t.Fatal(err)
}
canonical, err := service.IsCanonical(context.Background(), currentHead)
if err != nil {
t.Fatal(err)
}
if !canonical {
t.Error("Block should be canonical")
}
canonical, err = service.IsCanonical(context.Background(), oldHead)
if err != nil {
t.Fatal(err)
}
if !canonical {
t.Error("Block should be canonical")
}
canonical, err = service.IsCanonical(context.Background(), forkedRoot)
if err != nil {
t.Fatal(err)
}
if canonical {
t.Error("Block should not be canonical")
}
}
109 changes: 56 additions & 53 deletions beacon-chain/blockchain/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,40 +43,42 @@ import (
// Service represents a service that handles the internal
// logic of managing the full PoS beacon chain.
type Service struct {
ctx context.Context
cancel context.CancelFunc
beaconDB db.HeadAccessDatabase
depositCache *depositcache.DepositCache
chainStartFetcher powchain.ChainStartFetcher
attPool attestations.Pool
slashingPool *slashings.Pool
exitPool *voluntaryexits.Pool
genesisTime time.Time
p2p p2p.Broadcaster
maxRoutines int64
head *head
headLock sync.RWMutex
stateNotifier statefeed.Notifier
genesisRoot [32]byte
epochParticipation map[uint64]*precompute.Balance
epochParticipationLock sync.RWMutex
forkChoiceStore f.ForkChoicer
justifiedCheckpt *ethpb.Checkpoint
prevJustifiedCheckpt *ethpb.Checkpoint
bestJustifiedCheckpt *ethpb.Checkpoint
finalizedCheckpt *ethpb.Checkpoint
prevFinalizedCheckpt *ethpb.Checkpoint
nextEpochBoundarySlot uint64
voteLock sync.RWMutex
initSyncState map[[32]byte]*stateTrie.BeaconState
boundaryRoots [][32]byte
initSyncStateLock sync.RWMutex
checkpointState *cache.CheckpointStateCache
checkpointStateLock sync.Mutex
stateGen *stategen.State
opsService *attestations.Service
initSyncBlocks map[[32]byte]*ethpb.SignedBeaconBlock
initSyncBlocksLock sync.RWMutex
ctx context.Context
cancel context.CancelFunc
beaconDB db.HeadAccessDatabase
depositCache *depositcache.DepositCache
chainStartFetcher powchain.ChainStartFetcher
attPool attestations.Pool
slashingPool *slashings.Pool
exitPool *voluntaryexits.Pool
genesisTime time.Time
p2p p2p.Broadcaster
maxRoutines int64
head *head
headLock sync.RWMutex
stateNotifier statefeed.Notifier
genesisRoot [32]byte
epochParticipation map[uint64]*precompute.Balance
epochParticipationLock sync.RWMutex
forkChoiceStore f.ForkChoicer
justifiedCheckpt *ethpb.Checkpoint
prevJustifiedCheckpt *ethpb.Checkpoint
bestJustifiedCheckpt *ethpb.Checkpoint
finalizedCheckpt *ethpb.Checkpoint
prevFinalizedCheckpt *ethpb.Checkpoint
nextEpochBoundarySlot uint64
voteLock sync.RWMutex
initSyncState map[[32]byte]*stateTrie.BeaconState
boundaryRoots [][32]byte
initSyncStateLock sync.RWMutex
checkpointState *cache.CheckpointStateCache
checkpointStateLock sync.Mutex
stateGen *stategen.State
opsService *attestations.Service
initSyncBlocks map[[32]byte]*ethpb.SignedBeaconBlock
initSyncBlocksLock sync.RWMutex
recentCanonicalBlocks map[[32]byte]bool
recentCanonicalBlocksLock sync.RWMutex
}

// Config options for the service.
Expand All @@ -101,25 +103,26 @@ type Config struct {
func NewService(ctx context.Context, cfg *Config) (*Service, error) {
ctx, cancel := context.WithCancel(ctx)
return &Service{
ctx: ctx,
cancel: cancel,
beaconDB: cfg.BeaconDB,
depositCache: cfg.DepositCache,
chainStartFetcher: cfg.ChainStartFetcher,
attPool: cfg.AttPool,
exitPool: cfg.ExitPool,
slashingPool: cfg.SlashingPool,
p2p: cfg.P2p,
maxRoutines: cfg.MaxRoutines,
stateNotifier: cfg.StateNotifier,
epochParticipation: make(map[uint64]*precompute.Balance),
forkChoiceStore: cfg.ForkChoiceStore,
initSyncState: make(map[[32]byte]*stateTrie.BeaconState),
boundaryRoots: [][32]byte{},
checkpointState: cache.NewCheckpointStateCache(),
opsService: cfg.OpsService,
stateGen: cfg.StateGen,
initSyncBlocks: make(map[[32]byte]*ethpb.SignedBeaconBlock),
ctx: ctx,
cancel: cancel,
beaconDB: cfg.BeaconDB,
depositCache: cfg.DepositCache,
chainStartFetcher: cfg.ChainStartFetcher,
attPool: cfg.AttPool,
exitPool: cfg.ExitPool,
slashingPool: cfg.SlashingPool,
p2p: cfg.P2p,
maxRoutines: cfg.MaxRoutines,
stateNotifier: cfg.StateNotifier,
epochParticipation: make(map[uint64]*precompute.Balance),
forkChoiceStore: cfg.ForkChoiceStore,
initSyncState: make(map[[32]byte]*stateTrie.BeaconState),
boundaryRoots: [][32]byte{},
checkpointState: cache.NewCheckpointStateCache(),
opsService: cfg.OpsService,
stateGen: cfg.StateGen,
initSyncBlocks: make(map[[32]byte]*ethpb.SignedBeaconBlock),
recentCanonicalBlocks: make(map[[32]byte]bool),
}, nil
}

Expand Down
8 changes: 4 additions & 4 deletions beacon-chain/forkchoice/protoarray/ffg_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,12 @@ func setup(justifiedEpoch uint64, finalizedEpoch uint64) *ForkChoice {
f.store.nodeIndices[params.BeaconConfig().ZeroHash] = 0
f.store.nodes = append(f.store.nodes, &Node{
Slot: 0,
root: params.BeaconConfig().ZeroHash,
Parent: nonExistentNode,
Root: params.BeaconConfig().ZeroHash,
Parent: NonExistentNode,
justifiedEpoch: justifiedEpoch,
finalizedEpoch: finalizedEpoch,
bestChild: nonExistentNode,
BestDescendent: nonExistentNode,
bestChild: NonExistentNode,
BestDescendent: NonExistentNode,
Weight: 0,
})

Expand Down
4 changes: 2 additions & 2 deletions beacon-chain/forkchoice/protoarray/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ func copyNode(node *Node) *Node {
}

copiedRoot := [32]byte{}
copy(copiedRoot[:], node.root[:])
copy(copiedRoot[:], node.Root[:])

return &Node{
Slot: node.Slot,
root: copiedRoot,
Root: copiedRoot,
Parent: node.Parent,
justifiedEpoch: node.justifiedEpoch,
finalizedEpoch: node.finalizedEpoch,
Expand Down