mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Implement Beacon Node Validator and Observer Entry Points (#414)
This commit is contained in:
26
README.md
26
README.md
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -61,6 +61,7 @@ VERSION:
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
utils.SimulatorFlag,
|
||||
utils.ValidatorFlag,
|
||||
utils.VrcContractFlag,
|
||||
utils.PubKeyFlag,
|
||||
utils.Web3ProviderFlag,
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user