Skip to content

Commit

Permalink
Advance Beacon State Transition Part 2: Simulate Block Randao at Ever…
Browse files Browse the repository at this point in the history
…y Slot (#1252)
  • Loading branch information
rauljordan committed Jan 11, 2019
1 parent 1f11b82 commit 5258f3d
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 142 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
bazel-*

.DS_Store
.gitattributes

# IntelliJ
.idea
Expand Down
5 changes: 1 addition & 4 deletions beacon-chain/chaintest/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,5 @@ go_test(
srcs = ["yaml_test.go"],
data = glob(["tests/**"]),
embed = [":go_default_library"],
deps = [
"//beacon-chain/chaintest/backend:go_default_library",
"@com_github_go_yaml_yaml//:go_default_library",
],
deps = ["//beacon-chain/chaintest/backend:go_default_library"],
)
94 changes: 32 additions & 62 deletions beacon-chain/chaintest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,79 +8,49 @@ The test suite opts for YAML due to wide language support and support for inline

The testing format follows the official ETH2.0 Specification created [here](https://github.com/ethereum/eth2.0-specs/blob/master/specs/test-format.md)

### Core, Chain Tests
### Stateful Tests

Chain tests check for conformity of a certain client to the beacon chain specification for items such as the fork choice rule and Casper FFG validator rewards & penalties. Stateful tests need to specify a certain configuration of a beacon chain, with items such as the number validators, in the YAML file. Sample tests will all required fields are shown below.

**Fork Choice and Chain Updates**
**State Transition**

```yaml
The most important use case for this test format is to verify the ins and outs of the Ethereum Phase 0 Beacon Chain state advancement. The specification details very strict guidelines for blocks to successfully trigger a state transition, including items such as Casper Proof of Stake slashing conditions of validators, pseudorandomness in the form of RANDAO, and attestation on shard blocks being processed all inside each incoming beacon block. The YAML configuration for this test type allows for configuring a state transition run over N slots, triggering slashing conditions, processing deposits of new validators, and more.

An example state transition test for testing slot and block processing will look as follows:

title: Sample Ethereum 2.0 Beacon Chain Test
summary: Basic, functioning fork choice rule for Ethereum 2.0
```yaml
title: Sample Ethereum Serenity State Transition Tests
summary: Testing state transitions occurring over N slots with varying deposit sizes and proposal skips
test_suite: prysm
fork: tchaikovsky
version: 1.0
test_cases:
- config:
validator_count: 100
cycle_length: 8
shard_count: 32
min_committee_size: 8
slots:
# "slot_number" has a minimum of 1
- slot_number: 1
new_block:
id: A
# "*" is used for the genesis block
parent: "*"
attestations:
- block: A
# the following is a shorthand string for [0, 1, 2, 3, 4, 5]
validators: "0-5"
- slot_number: 2
new_block:
id: B
parent: A
attestations:
- block: B
validators: "0-5"
- slot_number: 3
new_block:
id: C
parent: A
attestations:
# attestation "committee_slot" defaults to the slot during which the attestation occurs
- block: C
validators: "2-7"
# default "committee_slot" can be directly overridden
- block: C
committee_slot: 2
validators: "6, 7"
- slot_number: 4
new_block:
id: D
parent: C
attestations:
- block: D
validators: "1-4"
# slots can be skipped entirely (5 in this case)
- slot_number: 6
new_block:
id: E
parent: D
attestations:
- block: E
validators: "0-4"
- block: B
validators: "5, 6, 7"
epoch_length: 64
deposits_for_chain_start: 1000
num_slots: 32 # Testing advancing state to slot < EpochLength
results:
head: E
last_justified_block: "*"
last_finalized_block: "*"
slot: 32
- config:
epoch_length: 64
deposits_for_chain_start: 16384
num_slots: 64 # Testing advancing state to exactly slot == EpochLength
results:
slot: 64
- config:
skip_slots: [10, 20, 30]
epoch_length: 64
deposits_for_chain_start: 1000
num_slots: 128 # Testing advancing state's slot == 2*EpochLength
results:
slot: 128
```
**Casper FFG Rewards/Penalties**
TODO
The following configuration options are available for state transition tests:
- **skip_slots**: `[int]` determines which slot numbers to simulate a proposer not submitting a block in the state transition TODO
- **epoch_length**: `int` the number of slots in an epoch
- **deposits_for_chain_start**: `int` the number of eth deposits needed for the beacon chain to initialize (this simulates an initial validator registry based on this number in the test)
- **num_slots**: `int` the number of times we run a state transition in the test

### Stateless Tests

Expand Down
4 changes: 3 additions & 1 deletion beacon-chain/chaintest/backend/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"chain_test_format.go",
"fork_choice_test_format.go",
"helpers.go",
"setup_db.go",
"shuffle_test_format.go",
"simulated_backend.go",
Expand All @@ -20,6 +21,7 @@ go_library(
"//proto/beacon/p2p/v1:go_default_library",
"//shared/hashutil:go_default_library",
"//shared/params:go_default_library",
"//shared/slices:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
package backend

// ChainTest --
type ChainTest struct {
// ForkChoiceTest --
type ForkChoiceTest struct {
Title string
Summary string
TestSuite string `yaml:"test_suite"`
TestCases []*ChainTestCase `yaml:"test_cases"`
TestSuite string `yaml:"test_suite"`
TestCases []*ForkChoiceTestCase `yaml:"test_cases"`
}

// ChainTestCase --
type ChainTestCase struct {
Config *ChainTestConfig `yaml:"config"`
Slots []*ChainTestSlot `yaml:"slots,flow"`
Results *ChainTestResults `yaml:"results"`
// ForkChoiceTestCase --
type ForkChoiceTestCase struct {
Config *ForkChoiceTestConfig `yaml:"config"`
Slots []*ForkChoiceTestSlot `yaml:"slots,flow"`
Results *ForkChoiceTestResult `yaml:"results"`
}

// ChainTestConfig --
type ChainTestConfig struct {
// ForkChoiceTestConfig --
type ForkChoiceTestConfig struct {
ValidatorCount uint64 `yaml:"validator_count"`
CycleLength uint64 `yaml:"cycle_length"`
ShardCount uint64 `yaml:"shard_count"`
MinCommitteeSize uint64 `yaml:"min_committee_size"`
}

// ChainTestSlot --
type ChainTestSlot struct {
// ForkChoiceTestSlot --
type ForkChoiceTestSlot struct {
SlotNumber uint64 `yaml:"slot_number"`
NewBlock *TestBlock `yaml:"new_block"`
Attestations []*TestAttestation `yaml:",flow"`
}

// ChainTestResults --
type ChainTestResults struct {
// ForkChoiceTestResult --
type ForkChoiceTestResult struct {
Head string
LastJustifiedBlock string `yaml:"last_justified_block"`
LastFinalizedBlock string `yaml:"last_finalized_block"`
Expand Down
122 changes: 122 additions & 0 deletions beacon-chain/chaintest/backend/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package backend

import (
"fmt"
"strconv"

"github.com/gogo/protobuf/proto"
b "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/hashutil"
"github.com/prysmaticlabs/prysm/shared/params"
)

// Generates a simulated beacon block to use
// in the next state transition given the current state,
// the previous beacon block, and previous beacon block root.
func generateSimulatedBlock(
beaconState *pb.BeaconState,
prevBlockRoot [32]byte,
randaoReveal [32]byte,
) (*pb.BeaconBlock, [32]byte, error) {
encodedState, err := proto.Marshal(beaconState)
if err != nil {
return nil, [32]byte{}, fmt.Errorf("could not marshal beacon state: %v", err)
}
stateRoot := hashutil.Hash(encodedState)
block := &pb.BeaconBlock{
Slot: beaconState.Slot + 1,
RandaoRevealHash32: randaoReveal[:],
ParentRootHash32: prevBlockRoot[:],
StateRootHash32: stateRoot[:],
Body: &pb.BeaconBlockBody{
ProposerSlashings: []*pb.ProposerSlashing{},
CasperSlashings: []*pb.CasperSlashing{},
Attestations: []*pb.Attestation{},
Deposits: []*pb.Deposit{},
Exits: []*pb.Exit{},
},
}
encodedBlock, err := proto.Marshal(block)
if err != nil {
return nil, [32]byte{}, fmt.Errorf("could not marshal new block: %v", err)
}
return block, hashutil.Hash(encodedBlock), nil
}

// Given a number of slots, we create a list of hash onions from an underlying randao reveal. For example,
// if we have N slots, we create a list of [secret, hash(secret), hash(hash(secret)), hash(...(prev N-1 hashes))].
func generateSimulatedRandaoHashOnions(numSlots uint64) [][32]byte {
// We create a list of randao hash onions for the given number of epochs
// we run the state transition.
numEpochs := numSlots % params.BeaconConfig().EpochLength
hashOnions := [][32]byte{params.BeaconConfig().SimulatedBlockRandao}

// We make the length of the hash onions list equal to the number of epochs + 10 to be safe.
for i := uint64(0); i < numEpochs+10; i++ {
prevHash := hashOnions[i]
hashOnions = append(hashOnions, hashutil.Hash(prevHash[:]))
}
return hashOnions
}

// This function determines the block randao reveal assuming there are no skipped slots,
// given a list of randao hash onions such as [pre-image, 0x01, 0x02, 0x03], for the
// 0th epoch, the block randao reveal will be 0x02 and the proposer commitment 0x03.
// The next epoch, the block randao reveal will be 0x01 and the commitment 0x02,
// so on and so forth until all randao layers are peeled off.
func determineSimulatedBlockRandaoReveal(layersPeeled int, hashOnions [][32]byte) [32]byte {
if layersPeeled == 0 {
return hashOnions[len(hashOnions)-2]
}
return hashOnions[len(hashOnions)-layersPeeled-2]
}

// Generates initial deposits for creating a beacon state in the simulated
// backend based on the yaml configuration.
func generateInitialSimulatedDeposits(randaoCommit [32]byte) ([]*pb.Deposit, error) {
genesisTime := params.BeaconConfig().GenesisTime.Unix()
deposits := make([]*pb.Deposit, params.BeaconConfig().DepositsForChainStart)
for i := 0; i < len(deposits); i++ {
depositInput := &pb.DepositInput{
Pubkey: []byte(strconv.Itoa(i)),
RandaoCommitmentHash32: randaoCommit[:],
}
depositData, err := b.EncodeDepositData(
depositInput,
params.BeaconConfig().MaxDepositInGwei,
genesisTime,
)
if err != nil {
return nil, fmt.Errorf("could not encode initial block deposits: %v", err)
}
deposits[i] = &pb.Deposit{DepositData: depositData}
}
return deposits, nil
}

// Finds the index of the next slot's proposer in the beacon state's
// validator set.
func findNextSlotProposerIndex(beaconState *pb.BeaconState) (uint32, error) {
nextSlot := beaconState.Slot + 1
epochLength := params.BeaconConfig().EpochLength
var earliestSlot uint64

// If the state slot is less than epochLength, then the earliestSlot would
// result in a negative number. Therefore we should default to
// earliestSlot = 0 in this case.
if nextSlot > epochLength {
earliestSlot = nextSlot - (nextSlot % epochLength) - epochLength
}

if nextSlot < earliestSlot || nextSlot >= earliestSlot+(epochLength*2) {
return 0, fmt.Errorf("slot %d out of bounds: %d <= slot < %d",
nextSlot,
earliestSlot,
earliestSlot+(epochLength*2),
)
}
committeeArray := beaconState.ShardCommitteesAtSlots[nextSlot-earliestSlot]
firstCommittee := committeeArray.ArrayShardCommittee[0].Committee
return firstCommittee[nextSlot%uint64(len(firstCommittee))], nil
}
Loading

0 comments on commit 5258f3d

Please sign in to comment.