Align eth1data Majority Vote with the spec (#7200)

* align voting with the spec
* Merge branch 'master' into eth1vote-spec-align
* remove redundant else statements
* add comment to exported variable
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge branch 'master' into eth1vote-spec-align

# Conflicts:
#	beacon-chain/rpc/validator/proposer_test.go
* refactor tests
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* fix mock POWChain
* move last block's time check and add more test cases
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
* Merge refs/heads/master into eth1vote-spec-align
This commit is contained in:
Radosław Kapka
2020-09-18 11:46:31 +02:00
committed by GitHub
parent 852082cdb4
commit cc147c7097
3 changed files with 601 additions and 501 deletions

View File

@@ -31,13 +31,25 @@ type POWChain struct {
GenesisEth1Block *big.Int
}
// GenesisTime represents a static past date - JAN 01 2000.
var GenesisTime = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
// NewPOWChain creates a new mock chain with empty block info.
func NewPOWChain() *POWChain {
return &POWChain{
HashesByHeight: make(map[int][]byte),
TimesByHeight: make(map[int]uint64),
BlockNumberByTime: make(map[uint64]*big.Int),
}
}
// Eth2GenesisPowchainInfo --
func (m *POWChain) Eth2GenesisPowchainInfo() (uint64, *big.Int) {
blk := m.GenesisEth1Block
if blk == nil {
blk = big.NewInt(0)
blk = big.NewInt(GenesisTime)
}
return uint64(time.Unix(0, 0).Unix()), blk
return uint64(GenesisTime), blk
}
// DepositTrie --
@@ -78,7 +90,15 @@ func (m *POWChain) BlockTimeByHeight(_ context.Context, height *big.Int) (uint64
// BlockNumberByTimestamp --
func (m *POWChain) BlockNumberByTimestamp(_ context.Context, time uint64) (*big.Int, error) {
return m.BlockNumberByTime[time], nil
var chosenTime uint64
var chosenNumber *big.Int
for t, num := range m.BlockNumberByTime {
if t > chosenTime && t <= time {
chosenNumber = num
chosenTime = t
}
}
return chosenNumber, nil
}
// DepositRoot --
@@ -132,3 +152,11 @@ func (r *RPCClient) BatchCall(b []rpc.BatchElem) error {
}
return nil
}
// InsertBlock adds provided block info into the chain.
func (m *POWChain) InsertBlock(height int, time uint64, hash []byte) *POWChain {
m.HashesByHeight[height] = hash
m.TimesByHeight[height] = time
m.BlockNumberByTime[time] = big.NewInt(int64(height))
return m
}

View File

@@ -221,22 +221,24 @@ func (vs *Server) eth1Data(ctx context.Context, slot uint64) (*ethpb.Eth1Data, e
return eth1Data, nil
}
// eth1DataMajorityVote determines the appropriate eth1data for a block proposal using an extended
// simple voting algorithm - voting with the majority. The algorithm for this method is as follows:
// - Determine the timestamp for the start slot for the current eth1 voting period.
// - Determine the timestamp for the start slot for the previous eth1 voting period.
// - Determine the most recent eth1 block before each timestamp.
// - Subtract the current period's eth1block.number by ETH1_FOLLOW_DISTANCE to determine the voting upper bound.
// - Subtract the previous period's eth1block.number by ETH1_FOLLOW_DISTANCE to determine the voting lower bound.
// eth1DataMajorityVote determines the appropriate eth1data for a block proposal using
// an algorithm called Voting with the Majority. The algorithm works as follows:
// - Determine the timestamp for the start slot for the eth1 voting period.
// - Determine the earliest and latest timestamps that a valid block can have.
// - Determine the first block not before the earliest timestamp. This block is the lower bound.
// - Determine the last block not after the latest timestamp. This block is the upper bound.
// - If the last block is too early, use current eth1data from the beacon state.
// - Filter out votes on unknown blocks and blocks which are outside of the range determined by the lower and upper bounds.
// - If no blocks are left after filtering, use the current period's most recent eth1 block for proposal.
// - Determine the vote with the highest count. Prefer the vote with the highest eth1 block height in the event of a tie.
// - This vote's block is the eth1block to use for the block proposal.
// - If no blocks are left after filtering votes, use eth1data from the latest valid block.
// - Otherwise:
// - Determine the vote with the highest count. Prefer the vote with the highest eth1 block height in the event of a tie.
// - This vote's block is the eth1 block to use for the block proposal.
func (vs *Server) eth1DataMajorityVote(ctx context.Context, beaconState *stateTrie.BeaconState) (*ethpb.Eth1Data, error) {
ctx, cancel := context.WithTimeout(ctx, eth1dataTimeout)
defer cancel()
slot := beaconState.Slot()
votingPeriodStartTime := vs.slotStartTime(slot)
if vs.MockEth1Votes {
return vs.mockETH1DataVote(ctx, slot)
@@ -246,55 +248,66 @@ func (vs *Server) eth1DataMajorityVote(ctx context.Context, beaconState *stateTr
}
eth1DataNotification = false
slotsPerVotingPeriod := params.BeaconConfig().EpochsPerEth1VotingPeriod * params.BeaconConfig().SlotsPerEpoch
currentPeriodVotingStartTime := vs.slotStartTime(slot)
// Can't use slotStartTime function because slot would be negative in the initial voting period.
previousPeriodVotingStartTime := currentPeriodVotingStartTime -
slotsPerVotingPeriod*params.BeaconConfig().SecondsPerSlot
currentPeriodBlockNumber, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, currentPeriodVotingStartTime)
if err != nil {
log.WithError(err).Error("Failed to get block number for current voting period")
return vs.randomETH1DataVote(ctx)
}
previousPeriodBlockNumber, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, previousPeriodVotingStartTime)
if err != nil {
log.WithError(err).Error("Failed to get block number for previous voting period")
return vs.randomETH1DataVote(ctx)
}
eth1FollowDistance := int64(params.BeaconConfig().Eth1FollowDistance)
currentPeriodInitialBlock := big.NewInt(0).Sub(currentPeriodBlockNumber, big.NewInt(eth1FollowDistance))
previousPeriodInitialBlock := big.NewInt(0).Sub(previousPeriodBlockNumber, big.NewInt(eth1FollowDistance))
earliestValidTime := votingPeriodStartTime - 2*params.BeaconConfig().SecondsPerETH1Block*uint64(eth1FollowDistance)
latestValidTime := votingPeriodStartTime - params.BeaconConfig().SecondsPerETH1Block*uint64(eth1FollowDistance)
currentDepositCount, _ := vs.DepositFetcher.DepositsNumberAndRootAtHeight(ctx, currentPeriodInitialBlock)
if currentDepositCount == 0 {
lastBlockByEarliestValidTime, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, earliestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get last block by earliest valid time")
return vs.randomETH1DataVote(ctx)
}
timeOfLastBlockByEarliestValidTime, err := vs.Eth1BlockFetcher.BlockTimeByHeight(ctx, lastBlockByEarliestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get time of last block by earliest valid time")
return vs.randomETH1DataVote(ctx)
}
// Increment the earliest block if the original block's time is before valid time.
// This is very likely to happen because BlockTimeByHeight returns the last block AT OR BEFORE the specified time.
if timeOfLastBlockByEarliestValidTime < earliestValidTime {
lastBlockByEarliestValidTime = big.NewInt(0).Add(lastBlockByEarliestValidTime, big.NewInt(1))
}
lastBlockByLatestValidTime, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, latestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get last block by latest valid time")
return vs.randomETH1DataVote(ctx)
}
timeOfLastBlockByLatestValidTime, err := vs.Eth1BlockFetcher.BlockTimeByHeight(ctx, lastBlockByLatestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get time of last block by latest valid time")
return vs.randomETH1DataVote(ctx)
}
if timeOfLastBlockByLatestValidTime < earliestValidTime {
return vs.HeadFetcher.HeadETH1Data(), nil
}
lastBlockDepositCount, lastBlockDepositRoot := vs.DepositFetcher.DepositsNumberAndRootAtHeight(ctx, lastBlockByLatestValidTime)
if lastBlockDepositCount == 0 {
return vs.ChainStartFetcher.ChainStartEth1Data(), nil
}
if len(beaconState.Eth1DataVotes()) == 0 {
eth1Data, err := vs.defaultEth1DataResponse(ctx, currentPeriodBlockNumber)
if err != nil {
log.WithError(err).Error("Failed to get eth1 data from current period block number")
return vs.randomETH1DataVote(ctx)
}
return eth1Data, nil
}
inRangeVotes, err := vs.inRangeVotes(ctx, beaconState, currentPeriodInitialBlock, previousPeriodInitialBlock)
inRangeVotes, err := vs.inRangeVotes(ctx, beaconState, lastBlockByEarliestValidTime, lastBlockByLatestValidTime)
if err != nil {
return nil, err
}
if len(inRangeVotes) == 0 {
eth1Data, err := vs.defaultEth1DataResponse(ctx, currentPeriodBlockNumber)
if err != nil {
log.WithError(err).Error("Failed to get eth1 data from current period block number")
return vs.randomETH1DataVote(ctx)
if lastBlockDepositCount >= vs.HeadFetcher.HeadETH1Data().DepositCount {
hash, err := vs.Eth1BlockFetcher.BlockHashByHeight(ctx, lastBlockByLatestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get hash of last block by latest valid time")
return vs.randomETH1DataVote(ctx)
}
return &ethpb.Eth1Data{
BlockHash: hash.Bytes(),
DepositCount: lastBlockDepositCount,
DepositRoot: lastBlockDepositRoot[:],
}, nil
}
return eth1Data, nil
return vs.HeadFetcher.HeadETH1Data(), nil
}
chosenVote := chosenEth1DataMajorityVote(inRangeVotes)
return &chosenVote.data.eth1Data, nil
}
@@ -308,8 +321,8 @@ func (vs *Server) slotStartTime(slot uint64) uint64 {
func (vs *Server) inRangeVotes(ctx context.Context,
beaconState *stateTrie.BeaconState,
currentPeriodInitialBlock *big.Int,
previousPeriodInitialBlock *big.Int) ([]eth1DataSingleVote, error) {
firstValidBlockNumber *big.Int,
lastValidBlockNumber *big.Int) ([]eth1DataSingleVote, error) {
currentETH1Data := vs.HeadFetcher.HeadETH1Data()
@@ -323,10 +336,10 @@ func (vs *Server) inRangeVotes(ctx context.Context,
if eth1Data.DepositCount < currentETH1Data.DepositCount {
continue
}
// previousPeriodInitialBlock.Cmp(height) < 1 filters out all blocks before previousPeriodInitialBlock
// currentPeriodInitialBlock.Cmp(height) > -1 filters out all blocks after currentPeriodInitialBlock
// These filters result in the range [previousPeriodInitialBlock, currentPeriodInitialBlock]
if ok && previousPeriodInitialBlock.Cmp(height) < 1 && currentPeriodInitialBlock.Cmp(height) > -1 {
// firstValidBlockNumber.Cmp(height) < 1 filters out all blocks before firstValidBlockNumber
// lastValidBlockNumber.Cmp(height) > -1 filters out all blocks after lastValidBlockNumber
// These filters result in the range [firstValidBlockNumber, lastValidBlockNumber]
if ok && firstValidBlockNumber.Cmp(height) < 1 && lastValidBlockNumber.Cmp(height) > -1 {
inRangeVotes = append(inRangeVotes, eth1DataSingleVote{eth1Data: *eth1Data, blockHeight: height})
}
}

File diff suppressed because it is too large Load Diff