Implement Beacon Node Validator and Observer Entry Points (#414)

This commit is contained in:
terence tsao
2018-08-20 08:50:11 -07:00
committed by GitHub
parent 5356bda411
commit e1f727cbb2
12 changed files with 452 additions and 76 deletions

View File

@@ -102,7 +102,7 @@ bazel build //...
Deploy the Validator Registration Contract into the chain of the running geth node by following the instructions [here](https://github.com/prysmaticlabs/prysm/blob/master/contracts/validator-registration-contract/deployVRC/README.md).
## Step 2: Running a Beacon Node
## Step 2a: Running a Beacon Node as a Validator or Observer
Make sure a geth node is running as a separate process according to the instructions from the previous section. Then, you can run a full beacon node as follows:
@@ -110,7 +110,8 @@ Make sure a geth node is running as a separate process according to the instruct
bazel run //beacon-chain --\
--web3provider ws://127.0.0.1:8546 \
--datadir /path/to/your/datadir \
--rpc-port 4000
--rpc-port 4000 \
--validator
```
This will spin up a full beacon node that connects to your running geth node, opens up an RPC connection for sharding clients to connect to it, and begins listening for p2p events.
@@ -122,12 +123,33 @@ bazel run //beacon-chain --\
--web3provider ws://127.0.0.1:8546 \
--datadir /path/to/your/datadir \
--rpc-port 4000 \
--validator \
--simulator \
--verbosity debug
```
Now, deposit ETH to become a validator in the contract using instructions [here](https://github.com/prysmaticlabs/prysm/blob/master/beacon-chain/VALIDATOR_REGISTER.md)
If you don't want to deposit ETH and become a validator, one option is to run a beacon node as an Observer. A beacon observer node has full privilege to listen in beacon chain and shard chains activities, but it will not participate
in validator duties such as proposing or attesting blocks. In addition, an observer node doesn't need to deposit 32ETH. To run an observer node, you discard the `--validator` flag.
```
bazel run //beacon-chain --\
--datadir /path/to/your/datadir \
--rpc-port 4000 \
```
or
```
bazel run //beacon-chain --\
--datadir /path/to/your/datadir \
--rpc-port 4000 \
--simulator \
--verbosity debug
```
## Step 3: Running a Sharding Client
Once your beacon node is up, you'll need to attach a sharding client as a separate process. This client is in charge of running attester/proposer responsibilities and handling shards (shards to be designed in phase 2). This client will listen for incoming beacon blocks and crystallized states and determine when its time to perform attester/proposer responsibilities accordingly.

View File

@@ -183,59 +183,103 @@ func (b *BeaconChain) IsEpochTransition(slotNumber uint64) bool {
return slotNumber >= b.CrystallizedState().LastStateRecalc()+params.CycleLength
}
// CanProcessBlock decides if an incoming p2p block can be processed into the chain's block trie.
func (b *BeaconChain) CanProcessBlock(fetcher types.POWBlockFetcher, block *types.Block) (bool, error) {
if _, err := fetcher.BlockByHash(context.Background(), block.PowChainRef()); err != nil {
return false, fmt.Errorf("fetching PoW block corresponding to mainchain reference failed: %v", err)
// CanProcessBlock is called to decide if an incoming p2p block can be processed into the chain's block trie,
// it checks time stamp, beacon chain parent block hash. It also checks pow chain reference hash if it's a validator.
func (b *BeaconChain) CanProcessBlock(fetcher types.POWBlockFetcher, block *types.Block, isValidator bool) (bool, error) {
if isValidator {
if _, err := fetcher.BlockByHash(context.Background(), block.PowChainRef()); err != nil {
return false, fmt.Errorf("fetching PoW block corresponding to mainchain reference failed: %v", err)
}
}
// Check if the parentHash pointed by the beacon block is in the beaconDB.
parentHash := block.ParentHash()
hasParent, err := b.db.Has(parentHash[:])
canProcess, err := b.verifyBlockParentHash(block)
if err != nil {
return false, err
return false, fmt.Errorf("unable to process block: %v", err)
}
// If the block does not have a parent in the database and if that parent is not the genesis block,
// then it fails the validity conditions.
if !hasParent && block.SlotNumber() != 1 {
return false, errors.New("parent hash points to nil in beaconDB")
if !canProcess {
return false, fmt.Errorf("parent block verification for beacon block %v failed", block.SlotNumber())
}
// Calculate the timestamp validity condition.
canProcess, err = b.verifyBlockTimeStamp(block)
if err != nil {
return false, fmt.Errorf("unable to process block: %v", err)
}
if !canProcess {
return false, fmt.Errorf("time stamp verification for beacon block %v failed", block.SlotNumber())
}
canProcess, err = b.verifyBlockActiveHash(block)
if err != nil {
return false, fmt.Errorf("unable to process block: %v", err)
}
if !canProcess {
return false, fmt.Errorf("active state verification for beacon block %v failed", block.SlotNumber())
}
canProcess, err = b.verifyBlockCrystallizedHash(block)
if err != nil {
return false, fmt.Errorf("unable to process block: %v", err)
}
if !canProcess {
return false, fmt.Errorf("crystallized verification for beacon block %v failed", block.SlotNumber())
}
return canProcess, nil
}
// verifyBlockTimeStamp verifies node's local time is greater than or equal to
// min timestamp as computed by GENESIS_TIME + slot_number * SLOT_DURATION.
func (b *BeaconChain) verifyBlockTimeStamp(block *types.Block) (bool, error) {
slotDuration := time.Duration(block.SlotNumber()*params.SlotDuration) * time.Second
genesis, err := b.GenesisBlock()
if err != nil {
return false, err
}
genesisTime, err := genesis.Timestamp()
if err != nil {
return false, err
}
if clock.Now().Before(genesisTime.Add(slotDuration)) {
return false, nil
}
return true, nil
}
// Verify state hashes from the block are correct.
// verifyBlockActiveHash verifies block's active state hash equal to
// node's computed active state hash.
func (b *BeaconChain) verifyBlockActiveHash(block *types.Block) (bool, error) {
hash, err := b.ActiveState().Hash()
if err != nil {
return false, err
}
if block.ActiveStateHash() != hash {
return false, fmt.Errorf("active state hash mismatched, wanted: %v, got: %v", block.ActiveStateHash(), hash)
return false, nil
}
return true, nil
}
hash, err = b.CrystallizedState().Hash()
// verifyBlockCrystallizedHash verifies block's crystallized state hash equal to
// node's computed crystallized state hash.
func (b *BeaconChain) verifyBlockCrystallizedHash(block *types.Block) (bool, error) {
hash, err := b.CrystallizedState().Hash()
if err != nil {
return false, err
}
if block.CrystallizedStateHash() != hash {
return false, fmt.Errorf("crystallized state hash mismatched, wanted: %v, got: %v", block.CrystallizedStateHash(), hash)
return false, nil
}
return true, nil
}
// verifyBlockParentHash verifies parentHash pointed by the beacon block is in the beaconDB.
func (b *BeaconChain) verifyBlockParentHash(block *types.Block) (bool, error) {
parentHash := block.ParentHash()
hasParent, err := b.db.Has(parentHash[:])
if err != nil {
return false, err
}
if !hasParent && block.SlotNumber() != 1 {
return false, errors.New("parent hash points to nil in beaconDB")
}
return true, nil
}
@@ -351,14 +395,11 @@ func (b *BeaconChain) calculateRewardsFFG(block *types.Block) error {
log.Info("Resetting attester bit field to all zeros")
b.ActiveState().ClearPendingAttestations()
b.CrystallizedState().SetValidators(validators)
err := b.PersistActiveState()
if err != nil {
if err := b.PersistActiveState(); err != nil {
return err
}
err = b.PersistCrystallizedState()
if err != nil {
if err := b.PersistCrystallizedState(); err != nil {
return err
}
}
@@ -449,8 +490,8 @@ func (b *BeaconChain) validatorsByHeightShard() ([]*beaconCommittee, error) {
return committees, nil
}
// getIndicesForSlot returns the attester set of a given height.
func (b *BeaconChain) getIndicesForHeight(height uint64) (*pb.ShardAndCommitteeArray, error) {
// GetIndicesForHeight returns the attester set of a given height.
func (b *BeaconChain) GetIndicesForHeight(height uint64) (*pb.ShardAndCommitteeArray, error) {
lcs := b.CrystallizedState().LastStateRecalc()
if !(lcs <= height && height < lcs+params.CycleLength*2) {
return nil, fmt.Errorf("can not return attester set of given height, input height %v has to be in between %v and %v", height, lcs, lcs+params.CycleLength*2)
@@ -458,8 +499,8 @@ func (b *BeaconChain) getIndicesForHeight(height uint64) (*pb.ShardAndCommitteeA
return b.CrystallizedState().IndicesForHeights()[height-lcs], nil
}
// getBlockHash returns the block hash of a given height.
func (b *BeaconChain) getBlockHash(slot, height uint64) ([]byte, error) {
// GetBlockHash returns the block hash of a given height.
func (b *BeaconChain) GetBlockHash(slot, height uint64) ([]byte, error) {
sback := slot - params.CycleLength*2
if !(sback <= height && height < sback+params.CycleLength*2) {
return nil, fmt.Errorf("can not return attester set of given height, input height %v has to be in between %v and %v", height, sback, sback+params.CycleLength*2)

View File

@@ -276,7 +276,7 @@ func TestCanProcessBlock(t *testing.T) {
block := NewBlock(t, &pb.BeaconBlock{
SlotNumber: 2,
})
if _, err := beaconChain.CanProcessBlock(&faultyFetcher{}, block); err == nil {
if _, err := beaconChain.CanProcessBlock(&faultyFetcher{}, block, true); err == nil {
t.Error("Using a faulty fetcher should throw an error, received nil")
}
@@ -302,8 +302,7 @@ func TestCanProcessBlock(t *testing.T) {
ParentHash: parentHash[:],
})
// A properly initialize block should not fail.
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block)
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
if err != nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
@@ -311,15 +310,45 @@ func TestCanProcessBlock(t *testing.T) {
t.Error("Should be able to process block, could not")
}
// Test timestamp validity condition.
// Negative scenario #1, invalid active hash
block = NewBlock(t, &pb.BeaconBlock{
SlotNumber: 2,
ActiveStateHash: []byte{'A'},
CrystallizedStateHash: crystallizedHash[:],
ParentHash: parentHash[:],
})
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
if err == nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
t.Error("Should not be able to process block with invalid active hash")
}
// Negative scenario #2, invalid crystallized hash
block = NewBlock(t, &pb.BeaconBlock{
SlotNumber: 2,
ActiveStateHash: activeHash[:],
CrystallizedStateHash: []byte{'A'},
ParentHash: parentHash[:],
})
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
if err == nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
t.Error("Should not be able to process block with invalid crystallied hash")
}
// Negative scenario #3, invalid timestamp
block = NewBlock(t, &pb.BeaconBlock{
SlotNumber: 1000000,
ActiveStateHash: activeHash[:],
CrystallizedStateHash: crystallizedHash[:],
ParentHash: parentHash[:],
})
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block)
if err != nil {
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
if err == nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
@@ -364,23 +393,23 @@ func TestProcessBlockWithBadHashes(t *testing.T) {
// Test negative scenario where active state hash is different than node's compute.
beaconChain.state.ActiveState = types.NewActiveState(&pb.ActiveState{RecentBlockHashes: [][]byte{{'B'}}})
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block)
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
if err == nil {
t.Fatal("CanProcessBlocks should have failed with diff state hashes")
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
t.Error("CanProcessBlocks should have returned false")
t.Error("CanProcessBlocks should have returned false with diff state hashes")
}
// Test negative scenario where crystallized state hash is different than node's compute.
beaconChain.state.CrystallizedState = types.NewCrystallizedState(&pb.CrystallizedState{LastStateRecalc: 9999})
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block)
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
if err == nil {
t.Fatal("CanProcessBlocks should have failed with diff state hashes")
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
t.Error("CanProcessBlocks should have returned false")
t.Error("CanProcessBlocks should have returned false with diff state hashes")
}
}
@@ -409,7 +438,7 @@ func TestProcessBlockWithInvalidParent(t *testing.T) {
ActiveStateHash: activeStateHash[:],
CrystallizedStateHash: crystallizedStateHash[:],
})
if _, err = beaconChain.CanProcessBlock(&mockFetcher{}, block); err == nil {
if _, err = beaconChain.CanProcessBlock(&mockFetcher{}, block, true); err == nil {
t.Error("Processing without a valid parent hash should fail")
}
@@ -428,17 +457,18 @@ func TestProcessBlockWithInvalidParent(t *testing.T) {
ParentHash: parentHash[:],
})
if _, err := beaconChain.CanProcessBlock(&mockFetcher{}, block); err == nil {
if _, err := beaconChain.CanProcessBlock(&mockFetcher{}, block, true); err == nil {
t.Error("Processing block should fail when parent hash is not in db")
}
if err = db.DB().Put(parentHash[:], nil); err != nil {
t.Fatalf("Failed to put parent block on db: %v", err)
}
if _, err = beaconChain.CanProcessBlock(&mockFetcher{}, block); err == nil {
_, err = beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
if err == nil {
t.Error("Processing block should fail when parent hash points to nil in db")
}
want := "parent hash points to nil in beaconDB"
want := "unable to process block: parent hash points to nil in beaconDB"
if err.Error() != want {
t.Errorf("invalid log, expected \"%s\", got \"%s\"", want, err.Error())
}
@@ -447,7 +477,7 @@ func TestProcessBlockWithInvalidParent(t *testing.T) {
t.Fatalf("Failed to put parent block on db: %v", err)
}
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block)
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
if err != nil {
t.Errorf("Should have been able to process block: %v", err)
}
@@ -738,6 +768,101 @@ func TestValidatorIndices(t *testing.T) {
}
}
func TestCanProcessBlockObserver(t *testing.T) {
beaconChain, db := startInMemoryBeaconChain(t)
defer db.Close()
clock = &fakeClock{}
// Initialize a parent block.
parentBlock := NewBlock(t, &pb.BeaconBlock{
SlotNumber: 1,
})
parentHash, err := parentBlock.Hash()
if err != nil {
t.Fatalf("Failed to compute parent block's hash: %v", err)
}
if err = db.DB().Put(parentHash[:], []byte{}); err != nil {
t.Fatalf("Failed to put parent block on db: %v", err)
}
// Initialize initial state.
activeState := types.NewActiveState(&pb.ActiveState{RecentBlockHashes: [][]byte{{'A'}}})
beaconChain.state.ActiveState = activeState
activeHash, err := activeState.Hash()
if err != nil {
t.Fatalf("Cannot hash active state: %v", err)
}
crystallized := types.NewCrystallizedState(&pb.CrystallizedState{})
beaconChain.state.CrystallizedState = crystallized
crystallizedHash, err := crystallized.Hash()
if err != nil {
t.Fatalf("Compute crystallized state hash failed: %v", err)
}
block := NewBlock(t, &pb.BeaconBlock{
SlotNumber: 2,
ActiveStateHash: activeHash[:],
CrystallizedStateHash: crystallizedHash[:],
ParentHash: parentHash[:],
})
// A properly initialize block should not fail.
canProcess, err := beaconChain.CanProcessBlock(nil, block, false)
if err != nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if !canProcess {
t.Error("Should be able to process block, could not")
}
// Negative scenario #1, invalid crystallized state hash
block = NewBlock(t, &pb.BeaconBlock{
SlotNumber: 2,
ActiveStateHash: activeHash[:],
CrystallizedStateHash: []byte{'A'},
ParentHash: parentHash[:],
})
canProcess, err = beaconChain.CanProcessBlock(nil, block, false)
if err == nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
t.Error("Should not be able to process block with invalid crystallized hash")
}
// Negative scenario #2, invalid active sate hash
block = NewBlock(t, &pb.BeaconBlock{
SlotNumber: 2,
ActiveStateHash: []byte{'A'},
CrystallizedStateHash: crystallizedHash[:],
ParentHash: parentHash[:],
})
canProcess, err = beaconChain.CanProcessBlock(nil, block, false)
if err == nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
t.Error("Should not be able to process block with invalid active hash")
}
// Negative scenario #3, invalid timestamp
block = NewBlock(t, &pb.BeaconBlock{
SlotNumber: 1000000,
ActiveStateHash: activeHash[:],
CrystallizedStateHash: crystallizedHash[:],
ParentHash: parentHash[:],
})
canProcess, err = beaconChain.CanProcessBlock(nil, block, false)
if err == nil {
t.Fatalf("CanProcessBlocks failed: %v", err)
}
if canProcess {
t.Error("Should not be able to process block with invalid timestamp condition")
}
}
func TestGetIndicesForHeight(t *testing.T) {
beaconChain, db := startInMemoryBeaconChain(t)
defer db.Close()
@@ -757,17 +882,17 @@ func TestGetIndicesForHeight(t *testing.T) {
if err := beaconChain.SetCrystallizedState(state); err != nil {
t.Fatalf("unable to mutate crystallized state: %v", err)
}
if _, err := beaconChain.getIndicesForHeight(1000); err == nil {
if _, err := beaconChain.GetIndicesForHeight(1000); err == nil {
t.Error("getIndicesForHeight should have failed with invalid height")
}
committee, err := beaconChain.getIndicesForHeight(1)
committee, err := beaconChain.GetIndicesForHeight(1)
if err != nil {
t.Errorf("getIndicesForHeight failed: %v", err)
}
if committee.ArrayShardAndCommittee[0].ShardId != 1 {
t.Errorf("getIndicesForHeight returns shardID should be 1, got: %v", committee.ArrayShardAndCommittee[0].ShardId)
}
committee, _ = beaconChain.getIndicesForHeight(2)
committee, _ = beaconChain.GetIndicesForHeight(2)
if committee.ArrayShardAndCommittee[0].ShardId != 3 {
t.Errorf("getIndicesForHeight returns shardID should be 3, got: %v", committee.ArrayShardAndCommittee[0].ShardId)
}
@@ -791,17 +916,17 @@ func TestGetBlockHash(t *testing.T) {
t.Fatalf("unable to mutate active state: %v", err)
}
if _, err := beaconChain.getBlockHash(200, 250); err == nil {
if _, err := beaconChain.GetBlockHash(200, 250); err == nil {
t.Error("getBlockHash should have failed with invalid height")
}
hash, err := beaconChain.getBlockHash(2*params.CycleLength, 0)
hash, err := beaconChain.GetBlockHash(2*params.CycleLength, 0)
if err != nil {
t.Errorf("getBlockHash failed: %v", err)
}
if bytes.Equal(hash, []byte{'A'}) {
t.Errorf("getBlockHash returns hash should be A, got: %v", hash)
}
hash, err = beaconChain.getBlockHash(2*params.CycleLength, uint64(len(beaconChain.ActiveState().RecentBlockHashes())-1))
hash, err = beaconChain.GetBlockHash(2*params.CycleLength, uint64(len(beaconChain.ActiveState().RecentBlockHashes())-1))
if err != nil {
t.Errorf("getBlockHash failed: %v", err)
}
@@ -810,6 +935,35 @@ func TestGetBlockHash(t *testing.T) {
}
}
func TestSaveBlockWithNil(t *testing.T) {
beaconChain, db := startInMemoryBeaconChain(t)
defer db.Close()
if err := beaconChain.saveBlock(&types.Block{}); err == nil {
t.Error("Save block should have failed with nil block")
}
}
func TestVerifyActiveHashWithNil(t *testing.T) {
beaconChain, db := startInMemoryBeaconChain(t)
defer db.Close()
beaconChain.SetActiveState(&types.ActiveState{})
_, err := beaconChain.verifyBlockActiveHash(&types.Block{})
if err == nil {
t.Error("Verify block hash should have failed with nil active state")
}
}
func TestVerifyCrystallizedHashWithNil(t *testing.T) {
beaconChain, db := startInMemoryBeaconChain(t)
defer db.Close()
beaconChain.SetCrystallizedState(&types.CrystallizedState{})
_, err := beaconChain.verifyBlockCrystallizedHash(&types.Block{})
if err == nil {
t.Error("Verify block hash should have failed with nil crystallized")
}
}
// NewBlock is a helper method to create blocks with valid defaults.
// For a generic block, use NewBlock(t, nil).
func NewBlock(t *testing.T, b *pb.BeaconBlock) *types.Block {

View File

@@ -4,7 +4,9 @@ package blockchain
import (
"context"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/prysmaticlabs/prysm/beacon-chain/powchain"
"github.com/prysmaticlabs/prysm/beacon-chain/types"
"github.com/prysmaticlabs/prysm/shared/database"
@@ -16,6 +18,7 @@ var log = logrus.WithField("prefix", "blockchain")
// ChainService represents a service that handles the internal
// logic of managing the full PoS beacon chain.
type ChainService struct {
validator bool
ctx context.Context
cancel context.CancelFunc
beaconDB *database.DB
@@ -47,12 +50,19 @@ func DefaultConfig() *Config {
// be registered into a running beacon node.
func NewChainService(ctx context.Context, cfg *Config, beaconChain *BeaconChain, beaconDB *database.DB, web3Service *powchain.Web3Service) (*ChainService, error) {
ctx, cancel := context.WithCancel(ctx)
var isValidator bool
if web3Service == nil {
isValidator = false
} else {
isValidator = true
}
return &ChainService{
ctx: ctx,
chain: beaconChain,
cancel: cancel,
beaconDB: beaconDB,
web3Service: web3Service,
validator: isValidator,
latestBeaconBlock: make(chan *types.Block, cfg.BeaconBlockBuf),
canonicalBlockEvent: make(chan *types.Block, cfg.AnnouncementBuf),
canonicalCrystallizedStateEvent: make(chan *types.CrystallizedState, cfg.AnnouncementBuf),
@@ -64,8 +74,11 @@ func NewChainService(ctx context.Context, cfg *Config, beaconChain *BeaconChain,
// Start a blockchain service's main event loop.
func (c *ChainService) Start() {
log.Infof("Starting service")
if c.validator {
log.Infof("Starting service as validator")
} else {
log.Infof("Starting service as observer")
}
go c.run(c.ctx.Done())
}
@@ -119,12 +132,20 @@ func (c *ChainService) ProcessedActiveStateHashes() [][32]byte {
// ProcessBlock accepts a new block for inclusion in the chain.
func (c *ChainService) ProcessBlock(block *types.Block) error {
var canProcess bool
var err error
h, err := block.Hash()
if err != nil {
return fmt.Errorf("could not hash incoming block: %v", err)
}
log.WithField("blockHash", fmt.Sprintf("0x%x", h)).Info("Received full block, processing validity conditions")
canProcess, err := c.chain.CanProcessBlock(c.web3Service.Client(), block)
// Process block as a validator if beacon node has registered, else process block as an observer.
if c.validator {
canProcess, err = c.chain.CanProcessBlock(c.web3Service.Client(), block, true)
} else {
canProcess, err = c.chain.CanProcessBlock(nil, block, false)
}
if err != nil {
// We might receive a lot of blocks that fail validity conditions,
// so we create a debug level log instead of an error log.
@@ -238,11 +259,12 @@ func (c *ChainService) run(done <-chan struct{}) {
}
}
// TODO: Using latest block hash for seed, this will eventually be replaced by randao.
// TODO: Using random hash based on time stamp for seed, this will eventually be replaced by VDF or RNG.
// TODO: Uncomment after there is a reasonable way to bootstrap validators into the
// protocol. For the first few blocks after genesis, the current approach below
// will panic as there are no registered validators.
activeState, err := c.chain.computeNewActiveState(c.web3Service.LatestBlockHash())
timestamp := time.Now().Unix()
activeState, err := c.chain.computeNewActiveState(common.BytesToHash([]byte(string(timestamp))))
if err != nil {
log.Errorf("Compute active state failed: %v", err)
}

View File

@@ -75,12 +75,16 @@ func TestStartStop(t *testing.T) {
if err != nil {
t.Fatalf("could not register blockchain service: %v", err)
}
chainService, err := NewChainService(ctx, cfg, beaconChain, db, web3Service)
chainService, err := NewChainService(ctx, cfg, beaconChain, db, nil)
if err != nil {
t.Fatalf("unable to setup chain service: %v", err)
}
chainService.Start()
chainService, err = NewChainService(ctx, cfg, beaconChain, db, web3Service)
if err != nil {
t.Fatalf("unable to setup chain service: %v", err)
}
chainService.Start()
if len(chainService.ProcessedBlockHashes()) != 0 {
@@ -114,6 +118,9 @@ func TestStartStop(t *testing.T) {
if hasState {
t.Errorf("has stored state should return false")
}
chainService.CanonicalBlockEvent()
chainService.CanonicalCrystallizedStateEvent()
chainService, _ = NewChainService(ctx, cfg, beaconChain, db, web3Service)
active := types.NewActiveState(&pb.ActiveState{RecentBlockHashes: [][]byte{{'A'}}})
@@ -275,6 +282,10 @@ func TestProcessingBadBlock(t *testing.T) {
}
chainService, _ := NewChainService(ctx, cfg, beaconChain, db, web3Service)
if err = chainService.ProcessBlock(&types.Block{}); err == nil {
t.Fatalf("Procss block should have failed with nil block")
}
active := types.NewActiveState(&pb.ActiveState{RecentBlockHashes: [][]byte{{'A'}}})
activeStateHash, err := active.Hash()
if err != nil {

View File

@@ -61,6 +61,7 @@ VERSION:
app.Flags = []cli.Flag{
utils.SimulatorFlag,
utils.ValidatorFlag,
utils.VrcContractFlag,
utils.PubKeyFlag,
utils.Web3ProviderFlag,

View File

@@ -29,5 +29,9 @@ go_test(
name = "go_default_test",
srcs = ["node_test.go"],
embed = [":go_default_library"],
deps = ["@com_github_urfave_cli//:go_default_library"],
deps = [
"//shared/testutil:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@com_github_urfave_cli//:go_default_library",
],
)

View File

@@ -60,11 +60,11 @@ func NewBeaconNode(ctx *cli.Context) (*BeaconNode, error) {
return nil, err
}
if err := beacon.registerPOWChainService(); err != nil {
if err := beacon.registerPOWChainService(ctx); err != nil {
return nil, err
}
if err := beacon.registerBlockchainService(); err != nil {
if err := beacon.registerBlockchainService(ctx); err != nil {
return nil, err
}
@@ -146,10 +146,13 @@ func (b *BeaconNode) registerP2P() error {
return b.services.RegisterService(beaconp2p)
}
func (b *BeaconNode) registerBlockchainService() error {
func (b *BeaconNode) registerBlockchainService(ctx *cli.Context) error {
var web3Service *powchain.Web3Service
if err := b.services.FetchService(&web3Service); err != nil {
return err
if ctx.GlobalBool(utils.ValidatorFlag.Name) {
if err := b.services.FetchService(&web3Service); err != nil {
return err
}
}
beaconChain, err := blockchain.NewBeaconChain(b.db.DB())
@@ -164,10 +167,14 @@ func (b *BeaconNode) registerBlockchainService() error {
return b.services.RegisterService(blockchainService)
}
func (b *BeaconNode) registerPOWChainService() error {
func (b *BeaconNode) registerPOWChainService(ctx *cli.Context) error {
if !ctx.GlobalBool(utils.ValidatorFlag.Name) {
return nil
}
rpcClient, err := gethRPC.Dial(b.ctx.GlobalString(utils.Web3ProviderFlag.Name))
if err != nil {
log.Errorf("Unable to connect to Geth node: %v", err)
log.Fatalf("Access to PoW chain is required for validator. Unable to connect to Geth node: %v", err)
}
powClient := ethclient.NewClient(rpcClient)
@@ -207,8 +214,11 @@ func (b *BeaconNode) registerSimulatorService(ctx *cli.Context) error {
}
var web3Service *powchain.Web3Service
if err := b.services.FetchService(&web3Service); err != nil {
return err
var isValidator = ctx.GlobalBool(utils.ValidatorFlag.Name)
if isValidator {
if err := b.services.FetchService(&web3Service); err != nil {
return err
}
}
var chainService *blockchain.ChainService
@@ -225,6 +235,7 @@ func (b *BeaconNode) registerSimulatorService(ctx *cli.Context) error {
P2P: p2pService,
Web3Service: web3Service,
ChainService: chainService,
Validator: isValidator,
}
simulatorService := simulator.NewSimulator(context.TODO(), cfg)
return b.services.RegisterService(simulatorService)

View File

@@ -4,13 +4,85 @@ import (
"flag"
"fmt"
"os"
"os/exec"
"testing"
"github.com/prysmaticlabs/prysm/shared/testutil"
logTest "github.com/sirupsen/logrus/hooks/test"
"github.com/urfave/cli"
)
// Test that the sharding node can build with default flag values.
func TestNode_Builds(t *testing.T) {
// Test that the beacon chain observer node can build with default flag values.
func TestNodeObserver_Builds(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("web3provider", "ws//127.0.0.1:8546", "web3 provider ws or IPC endpoint")
tmp := fmt.Sprintf("%s/datadir", os.TempDir())
set.String("datadir", tmp, "node data directory")
set.Bool("simulator", true, "want to be a simulator?")
context := cli.NewContext(app, set, nil)
_, err := NewBeaconNode(context)
if err != nil {
t.Fatalf("Failed to create BeaconNode: %v", err)
}
os.RemoveAll(tmp)
}
// Test that the beacon chain validator node build fails without PoW service.
func TestNodeValidator_Builds(t *testing.T) {
if os.Getenv("TEST_NODE_PANIC") == "1" {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("web3provider", "ws//127.0.0.1:8546", "web3 provider ws or IPC endpoint")
tmp := fmt.Sprintf("%s/datadir", os.TempDir())
set.String("datadir", tmp, "node data directory")
set.Bool("validator", true, "want to be a validator?")
context := cli.NewContext(app, set, nil)
NewBeaconNode(context)
}
// Start a subprocess to test beacon node crashes.
cmd := exec.Command(os.Args[0], "-test.run=TestNodeValidator_Builds")
cmd.Env = append(os.Environ(), "TEST_NODE_PANIC=1")
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
// Check beacon node program exited.
err := cmd.Wait()
if e, ok := err.(*exec.ExitError); !ok || e.Success() {
t.Fatalf("Process ran with err %v, want exit status 1", err)
}
}
// Test that beacon chain node can start.
func TestNodeStart(t *testing.T) {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("web3provider", "ws//127.0.0.1:8546", "web3 provider ws or IPC endpoint")
tmp := fmt.Sprintf("%s/datadir", os.TempDir())
set.String("datadir", tmp, "node data directory")
set.Bool("simulator", true, "want to be a simulator?")
context := cli.NewContext(app, set, nil)
node, err := NewBeaconNode(context)
if err != nil {
t.Fatalf("Failed to create BeaconNode: %v", err)
}
go node.Start()
os.RemoveAll(tmp)
}
// Test that beacon chain node can close.
func TestNodeClose(t *testing.T) {
hook := logTest.NewGlobal()
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("web3provider", "ws//127.0.0.1:8546", "web3 provider ws or IPC endpoint")
@@ -19,9 +91,14 @@ func TestNode_Builds(t *testing.T) {
context := cli.NewContext(app, set, nil)
_, err := NewBeaconNode(context)
node, err := NewBeaconNode(context)
if err != nil {
t.Fatalf("Failed to create BeaconNode: %v", err)
}
node.Close()
testutil.AssertLogsContain(t, hook, "Stopping beacon node")
os.RemoveAll(tmp)
}

View File

@@ -30,6 +30,7 @@ type Simulator struct {
beaconDB ethdb.Database
delay time.Duration
slotNum uint64
validator bool
broadcastedBlocks map[[32]byte]*types.Block
broadcastedBlockHashes [][32]byte
blockRequestChan chan p2p.Message
@@ -42,6 +43,7 @@ type Config struct {
Delay time.Duration
BlockRequestBuf int
CrystallizedStateRequestBuf int
Validator bool
P2P types.P2P
Web3Service types.POWChainService
ChainService types.StateFetcher
@@ -69,6 +71,7 @@ func NewSimulator(ctx context.Context, cfg *Config) *Simulator {
beaconDB: cfg.BeaconDB,
delay: cfg.Delay,
slotNum: 0,
validator: cfg.Validator,
broadcastedBlocks: make(map[[32]byte]*types.Block),
broadcastedBlockHashes: [][32]byte{},
blockRequestChan: make(chan p2p.Message, cfg.BlockRequestBuf),
@@ -202,10 +205,17 @@ func (sim *Simulator) run(delayChan <-chan time.Time, done <-chan struct{}) {
log.WithField("currentSlot", sim.slotNum).Info("Current slot")
var powChainRef []byte
if sim.validator {
powChainRef = sim.web3Service.LatestBlockHash().Bytes()
} else {
powChainRef = []byte{'N', '/', 'A'}
}
block := types.NewBlock(&pb.BeaconBlock{
SlotNumber: sim.slotNum,
Timestamp: ptypes.TimestampNow(),
PowChainRef: sim.web3Service.LatestBlockHash().Bytes(),
PowChainRef: powChainRef,
ActiveStateHash: activeStateHash[:],
CrystallizedStateHash: crystallizedStateHash[:],
ParentHash: parentHash,

View File

@@ -52,6 +52,7 @@ func TestLifecycle(t *testing.T) {
Web3Service: &mockPOWChainService{},
ChainService: &mockChainService{},
BeaconDB: db,
Validator: false,
}
sim := NewSimulator(context.Background(), cfg)
@@ -76,6 +77,7 @@ func TestBroadcastBlockHash(t *testing.T) {
Web3Service: &mockPOWChainService{},
ChainService: &mockChainService{},
BeaconDB: db,
Validator: false,
}
sim := NewSimulator(context.Background(), cfg)
@@ -111,6 +113,7 @@ func TestBlockRequest(t *testing.T) {
Web3Service: &mockPOWChainService{},
ChainService: &mockChainService{},
BeaconDB: db,
Validator: true,
}
sim := NewSimulator(context.Background(), cfg)
@@ -157,6 +160,7 @@ func TestBroadcastCrystallizedHash(t *testing.T) {
Web3Service: &mockPOWChainService{},
ChainService: &mockChainService{},
BeaconDB: db,
Validator: true,
}
sim := NewSimulator(context.Background(), cfg)
@@ -196,6 +200,7 @@ func TestCrystallizedRequest(t *testing.T) {
Web3Service: &mockPOWChainService{},
ChainService: &mockChainService{},
BeaconDB: db,
Validator: true,
}
sim := NewSimulator(context.Background(), cfg)
@@ -242,6 +247,7 @@ func TestLastSimulatedSession(t *testing.T) {
Web3Service: &mockPOWChainService{},
ChainService: &mockChainService{},
BeaconDB: db,
Validator: true,
}
sim := NewSimulator(context.Background(), cfg)
if err := db.Put([]byte("last-simulated-block"), []byte{}); err != nil {
@@ -251,3 +257,15 @@ func TestLastSimulatedSession(t *testing.T) {
t.Errorf("could not fetch last simulated session block: %v", err)
}
}
func TestDefaultConfig(t *testing.T) {
if DefaultConfig().BlockRequestBuf != 100 {
t.Errorf("incorrect default config for block request buffer")
}
if DefaultConfig().CrystallizedStateRequestBuf != 100 {
t.Errorf("incorrect default config for crystallized state request buffer")
}
if DefaultConfig().Delay != time.Second*5 {
t.Errorf("incorrect default config for delay")
}
}

View File

@@ -5,6 +5,11 @@ import (
)
var (
// ValidatorFlag determines if a node will run as validator. Participant should have deposited 32ETH and ready to perform proposer and attester duties.
ValidatorFlag = cli.BoolFlag{
Name: "validator",
Usage: "Whether or not to run the node as beacon chain validator",
}
// SimulatorFlag determines if a node will run only as a simulator service.
SimulatorFlag = cli.BoolFlag{
Name: "simulator",