mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
new: add bor consensus
This commit is contained in:
parent
1e56a697c0
commit
e5058f94fd
21 changed files with 3268 additions and 6 deletions
13
Makefile
13
Makefile
|
|
@ -11,6 +11,19 @@
|
|||
GOBIN = ./build/bin
|
||||
GO ?= latest
|
||||
GORUN = env GO111MODULE=on go run
|
||||
GOPATH = $(shell go env GOPATH)
|
||||
|
||||
bor:
|
||||
$(GORUN) build/ci.go install ./cmd/geth
|
||||
mkdir -p $(GOPATH)/bin/
|
||||
cp $(GOBIN)/geth $(GOBIN)/bor
|
||||
cp $(GOBIN)/* $(GOPATH)/bin/
|
||||
|
||||
bor-all:
|
||||
$(GORUN) build/ci.go install
|
||||
mkdir -p $(GOPATH)/bin/
|
||||
cp $(GOBIN)/geth $(GOBIN)/bor
|
||||
cp $(GOBIN)/* $(GOPATH)/bin/
|
||||
|
||||
geth:
|
||||
$(GORUN) build/ci.go install ./cmd/geth
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ const (
|
|||
MimetypeDataWithValidator = "data/validator"
|
||||
MimetypeTypedData = "data/typed"
|
||||
MimetypeClique = "application/x-clique-header"
|
||||
MimetypeBor = "application/x-bor-header"
|
||||
MimetypeTextPlain = "text/plain"
|
||||
)
|
||||
|
||||
|
|
|
|||
192
consensus/bor/api.go
Normal file
192
consensus/bor/api.go
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
package bor
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/xsleonard/go-merkle"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
var (
|
||||
// MaxCheckpointLength is the maximum number of blocks that can be requested for constructing a checkpoint root hash
|
||||
MaxCheckpointLength = uint64(math.Pow(2, 15))
|
||||
)
|
||||
|
||||
// API is a user facing RPC API to allow controlling the signer and voting
|
||||
// mechanisms of the proof-of-authority scheme.
|
||||
type API struct {
|
||||
chain consensus.ChainHeaderReader
|
||||
bor *Bor
|
||||
rootHashCache *lru.ARCCache
|
||||
}
|
||||
|
||||
// GetSnapshot retrieves the state snapshot at a given block.
|
||||
func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) {
|
||||
// Retrieve the requested block number (or current if none requested)
|
||||
var header *types.Header
|
||||
if number == nil || *number == rpc.LatestBlockNumber {
|
||||
header = api.chain.CurrentHeader()
|
||||
} else {
|
||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
||||
}
|
||||
// Ensure we have an actually valid block and return its snapshot
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
return api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
}
|
||||
|
||||
// GetAuthor retrieves the author a block.
|
||||
func (api *API) GetAuthor(number *rpc.BlockNumber) (*common.Address, error) {
|
||||
// Retrieve the requested block number (or current if none requested)
|
||||
var header *types.Header
|
||||
if number == nil || *number == rpc.LatestBlockNumber {
|
||||
header = api.chain.CurrentHeader()
|
||||
} else {
|
||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
||||
}
|
||||
// Ensure we have an actually valid block and return its snapshot
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
author, err := api.bor.Author(header)
|
||||
return &author, err
|
||||
}
|
||||
|
||||
// GetSnapshotAtHash retrieves the state snapshot at a given block.
|
||||
func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) {
|
||||
header := api.chain.GetHeaderByHash(hash)
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
return api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
}
|
||||
|
||||
// GetSigners retrieves the list of authorized signers at the specified block.
|
||||
func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) {
|
||||
// Retrieve the requested block number (or current if none requested)
|
||||
var header *types.Header
|
||||
if number == nil || *number == rpc.LatestBlockNumber {
|
||||
header = api.chain.CurrentHeader()
|
||||
} else {
|
||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
||||
}
|
||||
// Ensure we have an actually valid block and return the signers from its snapshot
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
snap, err := api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return snap.signers(), nil
|
||||
}
|
||||
|
||||
// GetSignersAtHash retrieves the list of authorized signers at the specified block.
|
||||
func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
|
||||
header := api.chain.GetHeaderByHash(hash)
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
snap, err := api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return snap.signers(), nil
|
||||
}
|
||||
|
||||
// GetCurrentProposer gets the current proposer
|
||||
func (api *API) GetCurrentProposer() (common.Address, error) {
|
||||
snap, err := api.GetSnapshot(nil)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
return snap.ValidatorSet.GetProposer().Address, nil
|
||||
}
|
||||
|
||||
// GetCurrentValidators gets the current validators
|
||||
func (api *API) GetCurrentValidators() ([]*Validator, error) {
|
||||
snap, err := api.GetSnapshot(nil)
|
||||
if err != nil {
|
||||
return make([]*Validator, 0), err
|
||||
}
|
||||
return snap.ValidatorSet.Validators, nil
|
||||
}
|
||||
|
||||
// GetRootHash returns the merkle root of the start to end block headers
|
||||
func (api *API) GetRootHash(start uint64, end uint64) (string, error) {
|
||||
if err := api.initializeRootHashCache(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
key := getRootHashKey(start, end)
|
||||
if root, known := api.rootHashCache.Get(key); known {
|
||||
return root.(string), nil
|
||||
}
|
||||
length := uint64(end - start + 1)
|
||||
if length > MaxCheckpointLength {
|
||||
return "", &MaxCheckpointLengthExceededError{start, end}
|
||||
}
|
||||
currentHeaderNumber := api.chain.CurrentHeader().Number.Uint64()
|
||||
if start > end || end > currentHeaderNumber {
|
||||
return "", &InvalidStartEndBlockError{start, end, currentHeaderNumber}
|
||||
}
|
||||
blockHeaders := make([]*types.Header, end-start+1)
|
||||
wg := new(sync.WaitGroup)
|
||||
concurrent := make(chan bool, 20)
|
||||
for i := start; i <= end; i++ {
|
||||
wg.Add(1)
|
||||
concurrent <- true
|
||||
go func(number uint64) {
|
||||
blockHeaders[number-start] = api.chain.GetHeaderByNumber(uint64(number))
|
||||
<-concurrent
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
close(concurrent)
|
||||
|
||||
headers := make([][32]byte, nextPowerOfTwo(length))
|
||||
for i := 0; i < len(blockHeaders); i++ {
|
||||
blockHeader := blockHeaders[i]
|
||||
header := crypto.Keccak256(appendBytes32(
|
||||
blockHeader.Number.Bytes(),
|
||||
new(big.Int).SetUint64(blockHeader.Time).Bytes(),
|
||||
blockHeader.TxHash.Bytes(),
|
||||
blockHeader.ReceiptHash.Bytes(),
|
||||
))
|
||||
|
||||
var arr [32]byte
|
||||
copy(arr[:], header)
|
||||
headers[i] = arr
|
||||
}
|
||||
|
||||
tree := merkle.NewTreeWithOpts(merkle.TreeOptions{EnableHashSorting: false, DisableHashLeaves: true})
|
||||
if err := tree.Generate(convert(headers), sha3.NewLegacyKeccak256()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
root := hex.EncodeToString(tree.Root().Hash)
|
||||
api.rootHashCache.Add(key, root)
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func (api *API) initializeRootHashCache() error {
|
||||
var err error
|
||||
if api.rootHashCache == nil {
|
||||
api.rootHashCache, err = lru.NewARC(10)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func getRootHashKey(start uint64, end uint64) string {
|
||||
return strconv.FormatUint(start, 10) + "-" + strconv.FormatUint(end, 10)
|
||||
}
|
||||
1305
consensus/bor/bor.go
Normal file
1305
consensus/bor/bor.go
Normal file
File diff suppressed because it is too large
Load diff
49
consensus/bor/clerk.go
Normal file
49
consensus/bor/clerk.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package bor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
// EventRecord represents state record
|
||||
type EventRecord struct {
|
||||
ID uint64 `json:"id" yaml:"id"`
|
||||
Contract common.Address `json:"contract" yaml:"contract"`
|
||||
Data hexutil.Bytes `json:"data" yaml:"data"`
|
||||
TxHash common.Hash `json:"tx_hash" yaml:"tx_hash"`
|
||||
LogIndex uint64 `json:"log_index" yaml:"log_index"`
|
||||
ChainID string `json:"bor_chain_id" yaml:"bor_chain_id"`
|
||||
}
|
||||
|
||||
type EventRecordWithTime struct {
|
||||
EventRecord
|
||||
Time time.Time `json:"record_time" yaml:"record_time"`
|
||||
}
|
||||
|
||||
// String returns the string representatin of span
|
||||
func (e *EventRecordWithTime) String() string {
|
||||
return fmt.Sprintf(
|
||||
"id %v, contract %v, data: %v, txHash: %v, logIndex: %v, chainId: %v, time %s",
|
||||
e.ID,
|
||||
e.Contract.String(),
|
||||
e.Data.String(),
|
||||
e.TxHash.Hex(),
|
||||
e.LogIndex,
|
||||
e.ChainID,
|
||||
e.Time.Format(time.RFC3339),
|
||||
)
|
||||
}
|
||||
|
||||
func (e *EventRecordWithTime) BuildEventRecord() *EventRecord {
|
||||
return &EventRecord{
|
||||
ID: e.ID,
|
||||
Contract: e.Contract,
|
||||
Data: e.Data,
|
||||
TxHash: e.TxHash,
|
||||
LogIndex: e.LogIndex,
|
||||
ChainID: e.ChainID,
|
||||
}
|
||||
}
|
||||
143
consensus/bor/errors.go
Normal file
143
consensus/bor/errors.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package bor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TotalVotingPowerExceededError is returned when the maximum allowed total voting power is exceeded
|
||||
type TotalVotingPowerExceededError struct {
|
||||
Sum int64
|
||||
Validators []*Validator
|
||||
}
|
||||
|
||||
func (e *TotalVotingPowerExceededError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Total voting power should be guarded to not exceed %v; got: %v; for validator set: %v",
|
||||
MaxTotalVotingPower,
|
||||
e.Sum,
|
||||
e.Validators,
|
||||
)
|
||||
}
|
||||
|
||||
type InvalidStartEndBlockError struct {
|
||||
Start uint64
|
||||
End uint64
|
||||
CurrentHeader uint64
|
||||
}
|
||||
|
||||
func (e *InvalidStartEndBlockError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Invalid parameters start: %d and end block: %d params",
|
||||
e.Start,
|
||||
e.End,
|
||||
)
|
||||
}
|
||||
|
||||
type MaxCheckpointLengthExceededError struct {
|
||||
Start uint64
|
||||
End uint64
|
||||
}
|
||||
|
||||
func (e *MaxCheckpointLengthExceededError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Start: %d and end block: %d exceed max allowed checkpoint length: %d",
|
||||
e.Start,
|
||||
e.End,
|
||||
MaxCheckpointLength,
|
||||
)
|
||||
}
|
||||
|
||||
// MismatchingValidatorsError is returned if a last block in sprint contains a
|
||||
// list of validators different from the one that local node calculated
|
||||
type MismatchingValidatorsError struct {
|
||||
Number uint64
|
||||
ValidatorSetSnap []byte
|
||||
ValidatorSetHeader []byte
|
||||
}
|
||||
|
||||
func (e *MismatchingValidatorsError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Mismatching validators at block %d\nValidatorBytes from snapshot: 0x%x\nValidatorBytes in Header: 0x%x\n",
|
||||
e.Number,
|
||||
e.ValidatorSetSnap,
|
||||
e.ValidatorSetHeader,
|
||||
)
|
||||
}
|
||||
|
||||
type BlockTooSoonError struct {
|
||||
Number uint64
|
||||
Succession int
|
||||
}
|
||||
|
||||
func (e *BlockTooSoonError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Block %d was created too soon. Signer turn-ness number is %d\n",
|
||||
e.Number,
|
||||
e.Succession,
|
||||
)
|
||||
}
|
||||
|
||||
// UnauthorizedProposerError is returned if a header is [being] signed by an unauthorized entity.
|
||||
type UnauthorizedProposerError struct {
|
||||
Number uint64
|
||||
Proposer []byte
|
||||
}
|
||||
|
||||
func (e *UnauthorizedProposerError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Proposer 0x%x is not a part of the producer set at block %d",
|
||||
e.Proposer,
|
||||
e.Number,
|
||||
)
|
||||
}
|
||||
|
||||
// UnauthorizedSignerError is returned if a header is [being] signed by an unauthorized entity.
|
||||
type UnauthorizedSignerError struct {
|
||||
Number uint64
|
||||
Signer []byte
|
||||
}
|
||||
|
||||
func (e *UnauthorizedSignerError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Signer 0x%x is not a part of the producer set at block %d",
|
||||
e.Signer,
|
||||
e.Number,
|
||||
)
|
||||
}
|
||||
|
||||
// WrongDifficultyError is returned if the difficulty of a block doesn't match the
|
||||
// turn of the signer.
|
||||
type WrongDifficultyError struct {
|
||||
Number uint64
|
||||
Expected uint64
|
||||
Actual uint64
|
||||
Signer []byte
|
||||
}
|
||||
|
||||
func (e *WrongDifficultyError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Wrong difficulty at block %d, expected: %d, actual %d. Signer was %x\n",
|
||||
e.Number,
|
||||
e.Expected,
|
||||
e.Actual,
|
||||
e.Signer,
|
||||
)
|
||||
}
|
||||
|
||||
type InvalidStateReceivedError struct {
|
||||
Number uint64
|
||||
LastStateID uint64
|
||||
To *time.Time
|
||||
Event *EventRecordWithTime
|
||||
}
|
||||
|
||||
func (e *InvalidStateReceivedError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Received invalid event %s at block %d. Requested events until %s. Last state id was %d",
|
||||
e.Event,
|
||||
e.Number,
|
||||
e.To.Format(time.RFC3339),
|
||||
e.LastStateID,
|
||||
)
|
||||
}
|
||||
103
consensus/bor/genesis_contracts_client.go
Normal file
103
consensus/bor/genesis_contracts_client.go
Normal file
File diff suppressed because one or more lines are too long
48
consensus/bor/merkle.go
Normal file
48
consensus/bor/merkle.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package bor
|
||||
|
||||
func appendBytes32(data ...[]byte) []byte {
|
||||
var result []byte
|
||||
for _, v := range data {
|
||||
paddedV, err := convertTo32(v)
|
||||
if err == nil {
|
||||
result = append(result, paddedV[:]...)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func nextPowerOfTwo(n uint64) uint64 {
|
||||
if n == 0 {
|
||||
return 1
|
||||
}
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
n--
|
||||
n |= n >> 1
|
||||
n |= n >> 2
|
||||
n |= n >> 4
|
||||
n |= n >> 8
|
||||
n |= n >> 16
|
||||
n |= n >> 32
|
||||
n++
|
||||
return n
|
||||
}
|
||||
|
||||
func convertTo32(input []byte) (output [32]byte, err error) {
|
||||
l := len(input)
|
||||
if l > 32 || l == 0 {
|
||||
return
|
||||
}
|
||||
copy(output[32-l:], input[:])
|
||||
return
|
||||
}
|
||||
|
||||
func convert(input []([32]byte)) [][]byte {
|
||||
var output [][]byte
|
||||
for _, in := range input {
|
||||
newInput := make([]byte, len(in[:]))
|
||||
copy(newInput, in[:])
|
||||
output = append(output, newInput)
|
||||
|
||||
}
|
||||
return output
|
||||
}
|
||||
139
consensus/bor/rest.go
Normal file
139
consensus/bor/rest.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package bor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
var (
|
||||
stateFetchLimit = 50
|
||||
)
|
||||
|
||||
// ResponseWithHeight defines a response object type that wraps an original
|
||||
// response with a height.
|
||||
type ResponseWithHeight struct {
|
||||
Height string `json:"height"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
}
|
||||
|
||||
type IHeimdallClient interface {
|
||||
Fetch(path string, query string) (*ResponseWithHeight, error)
|
||||
FetchWithRetry(path string, query string) (*ResponseWithHeight, error)
|
||||
FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error)
|
||||
}
|
||||
|
||||
type HeimdallClient struct {
|
||||
urlString string
|
||||
client http.Client
|
||||
}
|
||||
|
||||
func NewHeimdallClient(urlString string) (*HeimdallClient, error) {
|
||||
h := &HeimdallClient{
|
||||
urlString: urlString,
|
||||
client: http.Client{
|
||||
Timeout: time.Duration(5 * time.Second),
|
||||
},
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *HeimdallClient) FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error) {
|
||||
eventRecords := make([]*EventRecordWithTime, 0)
|
||||
for {
|
||||
queryParams := fmt.Sprintf("from-id=%d&to-time=%d&limit=%d", fromID, to, stateFetchLimit)
|
||||
log.Info("Fetching state sync events", "queryParams", queryParams)
|
||||
response, err := h.FetchWithRetry("clerk/event-record/list", queryParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var _eventRecords []*EventRecordWithTime
|
||||
if response.Result == nil { // status 204
|
||||
break
|
||||
}
|
||||
if err := json.Unmarshal(response.Result, &_eventRecords); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eventRecords = append(eventRecords, _eventRecords...)
|
||||
if len(_eventRecords) < stateFetchLimit {
|
||||
break
|
||||
}
|
||||
fromID += uint64(stateFetchLimit)
|
||||
}
|
||||
|
||||
sort.SliceStable(eventRecords, func(i, j int) bool {
|
||||
return eventRecords[i].ID < eventRecords[j].ID
|
||||
})
|
||||
return eventRecords, nil
|
||||
}
|
||||
|
||||
// Fetch fetches response from heimdall
|
||||
func (h *HeimdallClient) Fetch(rawPath string, rawQuery string) (*ResponseWithHeight, error) {
|
||||
u, err := url.Parse(h.urlString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u.Path = rawPath
|
||||
u.RawQuery = rawQuery
|
||||
|
||||
return h.internalFetch(u)
|
||||
}
|
||||
|
||||
// FetchWithRetry returns data from heimdall with retry
|
||||
func (h *HeimdallClient) FetchWithRetry(rawPath string, rawQuery string) (*ResponseWithHeight, error) {
|
||||
u, err := url.Parse(h.urlString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u.Path = rawPath
|
||||
u.RawQuery = rawQuery
|
||||
|
||||
for {
|
||||
res, err := h.internalFetch(u)
|
||||
if err == nil && res != nil {
|
||||
return res, nil
|
||||
}
|
||||
log.Info("Retrying again in 5 seconds for next Heimdall span", "path", u.Path)
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// internal fetch method
|
||||
func (h *HeimdallClient) internalFetch(u *url.URL) (*ResponseWithHeight, error) {
|
||||
res, err := h.client.Get(u.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// check status code
|
||||
if res.StatusCode != 200 && res.StatusCode != 204 {
|
||||
return nil, fmt.Errorf("Error while fetching data from Heimdall")
|
||||
}
|
||||
|
||||
// unmarshall data from buffer
|
||||
var response ResponseWithHeight
|
||||
if res.StatusCode == 204 {
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// get response
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
223
consensus/bor/snapshot.go
Normal file
223
consensus/bor/snapshot.go
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
package bor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// Snapshot is the state of the authorization voting at a given point in time.
|
||||
type Snapshot struct {
|
||||
config *params.BorConfig // Consensus engine parameters to fine tune behavior
|
||||
ethAPI *ethapi.PublicBlockChainAPI
|
||||
sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover
|
||||
|
||||
Number uint64 `json:"number"` // Block number where the snapshot was created
|
||||
Hash common.Hash `json:"hash"` // Block hash where the snapshot was created
|
||||
ValidatorSet *ValidatorSet `json:"validatorSet"` // Validator set at this moment
|
||||
Recents map[uint64]common.Address `json:"recents"` // Set of recent signers for spam protections
|
||||
}
|
||||
|
||||
// signersAscending implements the sort interface to allow sorting a list of addresses
|
||||
type signersAscending []common.Address
|
||||
|
||||
func (s signersAscending) Len() int { return len(s) }
|
||||
func (s signersAscending) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 }
|
||||
func (s signersAscending) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// newSnapshot creates a new snapshot with the specified startup parameters. This
|
||||
// method does not initialize the set of recent signers, so only ever use if for
|
||||
// the genesis block.
|
||||
func newSnapshot(
|
||||
config *params.BorConfig,
|
||||
sigcache *lru.ARCCache,
|
||||
number uint64,
|
||||
hash common.Hash,
|
||||
validators []*Validator,
|
||||
ethAPI *ethapi.PublicBlockChainAPI,
|
||||
) *Snapshot {
|
||||
snap := &Snapshot{
|
||||
config: config,
|
||||
ethAPI: ethAPI,
|
||||
sigcache: sigcache,
|
||||
Number: number,
|
||||
Hash: hash,
|
||||
ValidatorSet: NewValidatorSet(validators),
|
||||
Recents: make(map[uint64]common.Address),
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
// loadSnapshot loads an existing snapshot from the database.
|
||||
func loadSnapshot(config *params.BorConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash, ethAPI *ethapi.PublicBlockChainAPI) (*Snapshot, error) {
|
||||
blob, err := db.Get(append([]byte("bor-"), hash[:]...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snap := new(Snapshot)
|
||||
if err := json.Unmarshal(blob, snap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snap.config = config
|
||||
snap.sigcache = sigcache
|
||||
snap.ethAPI = ethAPI
|
||||
|
||||
// update total voting power
|
||||
if err := snap.ValidatorSet.updateTotalVotingPower(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// store inserts the snapshot into the database.
|
||||
func (s *Snapshot) store(db ethdb.Database) error {
|
||||
blob, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Put(append([]byte("bor-"), s.Hash[:]...), blob)
|
||||
}
|
||||
|
||||
// copy creates a deep copy of the snapshot, though not the individual votes.
|
||||
func (s *Snapshot) copy() *Snapshot {
|
||||
cpy := &Snapshot{
|
||||
config: s.config,
|
||||
ethAPI: s.ethAPI,
|
||||
sigcache: s.sigcache,
|
||||
Number: s.Number,
|
||||
Hash: s.Hash,
|
||||
ValidatorSet: s.ValidatorSet.Copy(),
|
||||
Recents: make(map[uint64]common.Address),
|
||||
}
|
||||
for block, signer := range s.Recents {
|
||||
cpy.Recents[block] = signer
|
||||
}
|
||||
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
|
||||
// Allow passing in no headers for cleaner code
|
||||
if len(headers) == 0 {
|
||||
return s, nil
|
||||
}
|
||||
// Sanity check that the headers can be applied
|
||||
for i := 0; i < len(headers)-1; i++ {
|
||||
if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 {
|
||||
return nil, errOutOfRangeChain
|
||||
}
|
||||
}
|
||||
if headers[0].Number.Uint64() != s.Number+1 {
|
||||
return nil, errOutOfRangeChain
|
||||
}
|
||||
// Iterate through the headers and create a new snapshot
|
||||
snap := s.copy()
|
||||
|
||||
for _, header := range headers {
|
||||
// Remove any votes on checkpoint blocks
|
||||
number := header.Number.Uint64()
|
||||
|
||||
// Delete the oldest signer from the recent list to allow it signing again
|
||||
if number >= s.config.Sprint && number-s.config.Sprint >= 0 {
|
||||
delete(snap.Recents, number-s.config.Sprint)
|
||||
}
|
||||
|
||||
// Resolve the authorization key and check against signers
|
||||
signer, err := ecrecover(header, s.sigcache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if signer is in validator set
|
||||
if !snap.ValidatorSet.HasAddress(signer.Bytes()) {
|
||||
return nil, &UnauthorizedSignerError{number, signer.Bytes()}
|
||||
}
|
||||
|
||||
if _, err = snap.GetSignerSuccessionNumber(signer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// add recents
|
||||
snap.Recents[number] = signer
|
||||
|
||||
// change validator set and change proposer
|
||||
if number > 0 && (number+1)%s.config.Sprint == 0 {
|
||||
if err := validateHeaderExtraField(header.Extra); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validatorBytes := header.Extra[extraVanity : len(header.Extra)-extraSeal]
|
||||
|
||||
// get validators from headers and use that for new validator set
|
||||
newVals, _ := ParseValidators(validatorBytes)
|
||||
v := getUpdatedValidatorSet(snap.ValidatorSet.Copy(), newVals)
|
||||
v.IncrementProposerPriority(1)
|
||||
snap.ValidatorSet = v
|
||||
}
|
||||
}
|
||||
snap.Number += uint64(len(headers))
|
||||
snap.Hash = headers[len(headers)-1].Hash()
|
||||
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// GetSignerSuccessionNumber returns the relative position of signer in terms of the in-turn proposer
|
||||
func (s *Snapshot) GetSignerSuccessionNumber(signer common.Address) (int, error) {
|
||||
validators := s.ValidatorSet.Validators
|
||||
proposer := s.ValidatorSet.GetProposer().Address
|
||||
proposerIndex, _ := s.ValidatorSet.GetByAddress(proposer)
|
||||
if proposerIndex == -1 {
|
||||
return -1, &UnauthorizedProposerError{s.Number, proposer.Bytes()}
|
||||
}
|
||||
signerIndex, _ := s.ValidatorSet.GetByAddress(signer)
|
||||
if signerIndex == -1 {
|
||||
return -1, &UnauthorizedSignerError{s.Number, signer.Bytes()}
|
||||
}
|
||||
|
||||
tempIndex := signerIndex
|
||||
if proposerIndex != tempIndex {
|
||||
if tempIndex < proposerIndex {
|
||||
tempIndex = tempIndex + len(validators)
|
||||
}
|
||||
}
|
||||
return tempIndex - proposerIndex, nil
|
||||
}
|
||||
|
||||
// signers retrieves the list of authorized signers in ascending order.
|
||||
func (s *Snapshot) signers() []common.Address {
|
||||
sigs := make([]common.Address, 0, len(s.ValidatorSet.Validators))
|
||||
for _, sig := range s.ValidatorSet.Validators {
|
||||
sigs = append(sigs, sig.Address)
|
||||
}
|
||||
return sigs
|
||||
}
|
||||
|
||||
// Difficulty returns the difficulty for a particular signer at the current snapshot number
|
||||
func (s *Snapshot) Difficulty(signer common.Address) uint64 {
|
||||
// if signer is empty
|
||||
if bytes.Compare(signer.Bytes(), common.Address{}.Bytes()) == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
validators := s.ValidatorSet.Validators
|
||||
proposer := s.ValidatorSet.GetProposer().Address
|
||||
totalValidators := len(validators)
|
||||
|
||||
proposerIndex, _ := s.ValidatorSet.GetByAddress(proposer)
|
||||
signerIndex, _ := s.ValidatorSet.GetByAddress(signer)
|
||||
|
||||
// temp index
|
||||
tempIndex := signerIndex
|
||||
if tempIndex < proposerIndex {
|
||||
tempIndex = tempIndex + totalValidators
|
||||
}
|
||||
|
||||
return uint64(totalValidators - (tempIndex - proposerIndex))
|
||||
}
|
||||
124
consensus/bor/snapshot_test.go
Normal file
124
consensus/bor/snapshot_test.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
package bor
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
const (
|
||||
numVals = 100
|
||||
)
|
||||
|
||||
func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) {
|
||||
validators := buildRandomValidatorSet(numVals)
|
||||
validatorSet := NewValidatorSet(validators)
|
||||
snap := Snapshot{
|
||||
ValidatorSet: validatorSet,
|
||||
}
|
||||
|
||||
// proposer is signer
|
||||
signer := validatorSet.Proposer.Address
|
||||
successionNumber, err := snap.GetSignerSuccessionNumber(signer)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
assert.Equal(t, 0, successionNumber)
|
||||
}
|
||||
|
||||
func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) {
|
||||
validators := buildRandomValidatorSet(numVals)
|
||||
|
||||
// sort validators by address, which is what NewValidatorSet also does
|
||||
sort.Sort(ValidatorsByAddress(validators))
|
||||
proposerIndex := 32
|
||||
signerIndex := 56
|
||||
// give highest ProposerPriority to a particular val, so that they become the proposer
|
||||
validators[proposerIndex].VotingPower = 200
|
||||
snap := Snapshot{
|
||||
ValidatorSet: NewValidatorSet(validators),
|
||||
}
|
||||
|
||||
// choose a signer at an index greater than proposer index
|
||||
signer := snap.ValidatorSet.Validators[signerIndex].Address
|
||||
successionNumber, err := snap.GetSignerSuccessionNumber(signer)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
assert.Equal(t, signerIndex-proposerIndex, successionNumber)
|
||||
}
|
||||
|
||||
func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) {
|
||||
validators := buildRandomValidatorSet(numVals)
|
||||
proposerIndex := 98
|
||||
signerIndex := 11
|
||||
// give highest ProposerPriority to a particular val, so that they become the proposer
|
||||
validators[proposerIndex].VotingPower = 200
|
||||
snap := Snapshot{
|
||||
ValidatorSet: NewValidatorSet(validators),
|
||||
}
|
||||
|
||||
// choose a signer at an index greater than proposer index
|
||||
signer := snap.ValidatorSet.Validators[signerIndex].Address
|
||||
successionNumber, err := snap.GetSignerSuccessionNumber(signer)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
assert.Equal(t, signerIndex+numVals-proposerIndex, successionNumber)
|
||||
}
|
||||
|
||||
func TestGetSignerSuccessionNumber_ProposerNotFound(t *testing.T) {
|
||||
validators := buildRandomValidatorSet(numVals)
|
||||
snap := Snapshot{
|
||||
ValidatorSet: NewValidatorSet(validators),
|
||||
}
|
||||
dummyProposerAddress := randomAddress()
|
||||
snap.ValidatorSet.Proposer = &Validator{Address: dummyProposerAddress}
|
||||
// choose any signer
|
||||
signer := snap.ValidatorSet.Validators[3].Address
|
||||
_, err := snap.GetSignerSuccessionNumber(signer)
|
||||
assert.NotNil(t, err)
|
||||
e, ok := err.(*UnauthorizedProposerError)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, dummyProposerAddress.Bytes(), e.Proposer)
|
||||
}
|
||||
|
||||
func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) {
|
||||
validators := buildRandomValidatorSet(numVals)
|
||||
snap := Snapshot{
|
||||
ValidatorSet: NewValidatorSet(validators),
|
||||
}
|
||||
dummySignerAddress := randomAddress()
|
||||
_, err := snap.GetSignerSuccessionNumber(dummySignerAddress)
|
||||
assert.NotNil(t, err)
|
||||
e, ok := err.(*UnauthorizedSignerError)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, dummySignerAddress.Bytes(), e.Signer)
|
||||
}
|
||||
|
||||
func buildRandomValidatorSet(numVals int) []*Validator {
|
||||
rand.Seed(time.Now().Unix())
|
||||
validators := make([]*Validator, numVals)
|
||||
for i := 0; i < numVals; i++ {
|
||||
validators[i] = &Validator{
|
||||
Address: randomAddress(),
|
||||
// cannot process validators with voting power 0, hence +1
|
||||
VotingPower: int64(rand.Intn(99) + 1),
|
||||
}
|
||||
}
|
||||
|
||||
// sort validators by address, which is what NewValidatorSet also does
|
||||
sort.Sort(ValidatorsByAddress(validators))
|
||||
return validators
|
||||
}
|
||||
|
||||
func randomAddress() common.Address {
|
||||
bytes := make([]byte, 32)
|
||||
rand.Read(bytes)
|
||||
return common.BytesToAddress(bytes)
|
||||
}
|
||||
16
consensus/bor/span.go
Normal file
16
consensus/bor/span.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package bor
|
||||
|
||||
// Span represents a current bor span
|
||||
type Span struct {
|
||||
ID uint64 `json:"span_id" yaml:"span_id"`
|
||||
StartBlock uint64 `json:"start_block" yaml:"start_block"`
|
||||
EndBlock uint64 `json:"end_block" yaml:"end_block"`
|
||||
}
|
||||
|
||||
// HeimdallSpan represents span from heimdall APIs
|
||||
type HeimdallSpan struct {
|
||||
Span
|
||||
ValidatorSet ValidatorSet `json:"validator_set" yaml:"validator_set"`
|
||||
SelectedProducers []Validator `json:"selected_producers" yaml:"selected_producers"`
|
||||
ChainID string `json:"bor_chain_id" yaml:"bor_chain_id"`
|
||||
}
|
||||
154
consensus/bor/validator.go
Normal file
154
consensus/bor/validator.go
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
package bor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
// "encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// Validator represets Volatile state for each Validator
|
||||
type Validator struct {
|
||||
ID uint64 `json:"ID"`
|
||||
Address common.Address `json:"signer"`
|
||||
VotingPower int64 `json:"power"`
|
||||
ProposerPriority int64 `json:"accum"`
|
||||
}
|
||||
|
||||
// NewValidator creates new validator
|
||||
func NewValidator(address common.Address, votingPower int64) *Validator {
|
||||
return &Validator{
|
||||
Address: address,
|
||||
VotingPower: votingPower,
|
||||
ProposerPriority: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Copy creates a new copy of the validator so we can mutate ProposerPriority.
|
||||
// Panics if the validator is nil.
|
||||
func (v *Validator) Copy() *Validator {
|
||||
vCopy := *v
|
||||
return &vCopy
|
||||
}
|
||||
|
||||
// Cmp returns the one validator with a higher ProposerPriority.
|
||||
// If ProposerPriority is same, it returns the validator with lexicographically smaller address
|
||||
func (v *Validator) Cmp(other *Validator) *Validator {
|
||||
// if both of v and other are nil, nil will be returned and that could possibly lead to nil pointer dereference bubbling up the stack
|
||||
if v == nil {
|
||||
return other
|
||||
}
|
||||
if other == nil {
|
||||
return v
|
||||
}
|
||||
if v.ProposerPriority > other.ProposerPriority {
|
||||
return v
|
||||
} else if v.ProposerPriority < other.ProposerPriority {
|
||||
return other
|
||||
} else {
|
||||
result := bytes.Compare(v.Address.Bytes(), other.Address.Bytes())
|
||||
if result < 0 {
|
||||
return v
|
||||
} else if result > 0 {
|
||||
return other
|
||||
} else {
|
||||
panic("Cannot compare identical validators")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) String() string {
|
||||
if v == nil {
|
||||
return "nil-Validator"
|
||||
}
|
||||
return fmt.Sprintf("Validator{%v Power:%v Priority:%v}",
|
||||
v.Address.Hex(),
|
||||
v.VotingPower,
|
||||
v.ProposerPriority)
|
||||
}
|
||||
|
||||
// ValidatorListString returns a prettified validator list for logging purposes.
|
||||
func ValidatorListString(vals []*Validator) string {
|
||||
chunks := make([]string, len(vals))
|
||||
for i, val := range vals {
|
||||
chunks[i] = fmt.Sprintf("%s:%d", val.Address, val.VotingPower)
|
||||
}
|
||||
|
||||
return strings.Join(chunks, ",")
|
||||
}
|
||||
|
||||
// HeaderBytes return header bytes
|
||||
func (v *Validator) HeaderBytes() []byte {
|
||||
result := make([]byte, 40)
|
||||
copy(result[:20], v.Address.Bytes())
|
||||
copy(result[20:], v.PowerBytes())
|
||||
return result
|
||||
}
|
||||
|
||||
// PowerBytes return power bytes
|
||||
func (v *Validator) PowerBytes() []byte {
|
||||
powerBytes := big.NewInt(0).SetInt64(v.VotingPower).Bytes()
|
||||
result := make([]byte, 20)
|
||||
copy(result[20-len(powerBytes):], powerBytes)
|
||||
return result
|
||||
}
|
||||
|
||||
// MinimalVal returns block number of last validator update
|
||||
func (v *Validator) MinimalVal() MinimalVal {
|
||||
return MinimalVal{
|
||||
ID: v.ID,
|
||||
VotingPower: uint64(v.VotingPower),
|
||||
Signer: v.Address,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseValidators returns validator set bytes
|
||||
func ParseValidators(validatorsBytes []byte) ([]*Validator, error) {
|
||||
if len(validatorsBytes)%40 != 0 {
|
||||
return nil, errors.New("Invalid validators bytes")
|
||||
}
|
||||
|
||||
result := make([]*Validator, len(validatorsBytes)/40)
|
||||
for i := 0; i < len(validatorsBytes); i += 40 {
|
||||
address := make([]byte, 20)
|
||||
power := make([]byte, 20)
|
||||
|
||||
copy(address, validatorsBytes[i:i+20])
|
||||
copy(power, validatorsBytes[i+20:i+40])
|
||||
|
||||
result[i/40] = NewValidator(common.BytesToAddress(address), big.NewInt(0).SetBytes(power).Int64())
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
// MinimalVal is the minimal validator representation
|
||||
// Used to send validator information to bor validator contract
|
||||
type MinimalVal struct {
|
||||
ID uint64 `json:"ID"`
|
||||
VotingPower uint64 `json:"power"` // TODO add 10^-18 here so that we dont overflow easily
|
||||
Signer common.Address `json:"signer"`
|
||||
}
|
||||
|
||||
// SortMinimalValByAddress sorts validators
|
||||
func SortMinimalValByAddress(a []MinimalVal) []MinimalVal {
|
||||
sort.Slice(a, func(i, j int) bool {
|
||||
return bytes.Compare(a[i].Signer.Bytes(), a[j].Signer.Bytes()) < 0
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// ValidatorsToMinimalValidators converts array of validators to minimal validators
|
||||
func ValidatorsToMinimalValidators(vals []Validator) (minVals []MinimalVal) {
|
||||
for _, val := range vals {
|
||||
minVals = append(minVals, val.MinimalVal())
|
||||
}
|
||||
return
|
||||
}
|
||||
703
consensus/bor/validator_set.go
Normal file
703
consensus/bor/validator_set.go
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
package bor
|
||||
|
||||
// Tendermint leader selection algorithm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// MaxTotalVotingPower - the maximum allowed total voting power.
|
||||
// It needs to be sufficiently small to, in all cases:
|
||||
// 1. prevent clipping in incrementProposerPriority()
|
||||
// 2. let (diff+diffMax-1) not overflow in IncrementProposerPriority()
|
||||
// (Proof of 1 is tricky, left to the reader).
|
||||
// It could be higher, but this is sufficiently large for our purposes,
|
||||
// and leaves room for defensive purposes.
|
||||
// PriorityWindowSizeFactor - is a constant that when multiplied with the total voting power gives
|
||||
// the maximum allowed distance between validator priorities.
|
||||
|
||||
const (
|
||||
MaxTotalVotingPower = int64(math.MaxInt64) / 8
|
||||
PriorityWindowSizeFactor = 2
|
||||
)
|
||||
|
||||
// ValidatorSet represent a set of *Validator at a given height.
|
||||
// The validators can be fetched by address or index.
|
||||
// The index is in order of .Address, so the indices are fixed
|
||||
// for all rounds of a given blockchain height - ie. the validators
|
||||
// are sorted by their address.
|
||||
// On the other hand, the .ProposerPriority of each validator and
|
||||
// the designated .GetProposer() of a set changes every round,
|
||||
// upon calling .IncrementProposerPriority().
|
||||
// NOTE: Not goroutine-safe.
|
||||
// NOTE: All get/set to validators should copy the value for safety.
|
||||
type ValidatorSet struct {
|
||||
// NOTE: persisted via reflect, must be exported.
|
||||
Validators []*Validator `json:"validators"`
|
||||
Proposer *Validator `json:"proposer"`
|
||||
|
||||
// cached (unexported)
|
||||
totalVotingPower int64
|
||||
}
|
||||
|
||||
// NewValidatorSet initializes a ValidatorSet by copying over the
|
||||
// values from `valz`, a list of Validators. If valz is nil or empty,
|
||||
// the new ValidatorSet will have an empty list of Validators.
|
||||
// The addresses of validators in `valz` must be unique otherwise the
|
||||
// function panics.
|
||||
func NewValidatorSet(valz []*Validator) *ValidatorSet {
|
||||
vals := &ValidatorSet{}
|
||||
err := vals.updateWithChangeSet(valz, false)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot create validator set: %s", err))
|
||||
}
|
||||
if len(valz) > 0 {
|
||||
vals.IncrementProposerPriority(1)
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// Nil or empty validator sets are invalid.
|
||||
func (vals *ValidatorSet) IsNilOrEmpty() bool {
|
||||
return vals == nil || len(vals.Validators) == 0
|
||||
}
|
||||
|
||||
// Increment ProposerPriority and update the proposer on a copy, and return it.
|
||||
func (vals *ValidatorSet) CopyIncrementProposerPriority(times int) *ValidatorSet {
|
||||
copy := vals.Copy()
|
||||
copy.IncrementProposerPriority(times)
|
||||
return copy
|
||||
}
|
||||
|
||||
// IncrementProposerPriority increments ProposerPriority of each validator and updates the
|
||||
// proposer. Panics if validator set is empty.
|
||||
// `times` must be positive.
|
||||
func (vals *ValidatorSet) IncrementProposerPriority(times int) {
|
||||
if vals.IsNilOrEmpty() {
|
||||
panic("empty validator set")
|
||||
}
|
||||
if times <= 0 {
|
||||
panic("Cannot call IncrementProposerPriority with non-positive times")
|
||||
}
|
||||
|
||||
// Cap the difference between priorities to be proportional to 2*totalPower by
|
||||
// re-normalizing priorities, i.e., rescale all priorities by multiplying with:
|
||||
// 2*totalVotingPower/(maxPriority - minPriority)
|
||||
diffMax := PriorityWindowSizeFactor * vals.TotalVotingPower()
|
||||
vals.RescalePriorities(diffMax)
|
||||
vals.shiftByAvgProposerPriority()
|
||||
|
||||
var proposer *Validator
|
||||
// Call IncrementProposerPriority(1) times times.
|
||||
for i := 0; i < times; i++ {
|
||||
proposer = vals.incrementProposerPriority()
|
||||
}
|
||||
|
||||
vals.Proposer = proposer
|
||||
}
|
||||
|
||||
func (vals *ValidatorSet) RescalePriorities(diffMax int64) {
|
||||
if vals.IsNilOrEmpty() {
|
||||
panic("empty validator set")
|
||||
}
|
||||
// NOTE: This check is merely a sanity check which could be
|
||||
// removed if all tests would init. voting power appropriately;
|
||||
// i.e. diffMax should always be > 0
|
||||
if diffMax <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculating ceil(diff/diffMax):
|
||||
// Re-normalization is performed by dividing by an integer for simplicity.
|
||||
// NOTE: This may make debugging priority issues easier as well.
|
||||
diff := computeMaxMinPriorityDiff(vals)
|
||||
ratio := (diff + diffMax - 1) / diffMax
|
||||
if diff > diffMax {
|
||||
for _, val := range vals.Validators {
|
||||
val.ProposerPriority = val.ProposerPriority / ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (vals *ValidatorSet) incrementProposerPriority() *Validator {
|
||||
for _, val := range vals.Validators {
|
||||
// Check for overflow for sum.
|
||||
newPrio := safeAddClip(val.ProposerPriority, val.VotingPower)
|
||||
val.ProposerPriority = newPrio
|
||||
}
|
||||
// Decrement the validator with most ProposerPriority.
|
||||
mostest := vals.getValWithMostPriority()
|
||||
// Mind the underflow.
|
||||
mostest.ProposerPriority = safeSubClip(mostest.ProposerPriority, vals.TotalVotingPower())
|
||||
|
||||
return mostest
|
||||
}
|
||||
|
||||
// Should not be called on an empty validator set.
|
||||
func (vals *ValidatorSet) computeAvgProposerPriority() int64 {
|
||||
n := int64(len(vals.Validators))
|
||||
sum := big.NewInt(0)
|
||||
for _, val := range vals.Validators {
|
||||
sum.Add(sum, big.NewInt(val.ProposerPriority))
|
||||
}
|
||||
avg := sum.Div(sum, big.NewInt(n))
|
||||
if avg.IsInt64() {
|
||||
return avg.Int64()
|
||||
}
|
||||
|
||||
// This should never happen: each val.ProposerPriority is in bounds of int64.
|
||||
panic(fmt.Sprintf("Cannot represent avg ProposerPriority as an int64 %v", avg))
|
||||
}
|
||||
|
||||
// Compute the difference between the max and min ProposerPriority of that set.
|
||||
func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 {
|
||||
if vals.IsNilOrEmpty() {
|
||||
panic("empty validator set")
|
||||
}
|
||||
max := int64(math.MinInt64)
|
||||
min := int64(math.MaxInt64)
|
||||
for _, v := range vals.Validators {
|
||||
if v.ProposerPriority < min {
|
||||
min = v.ProposerPriority
|
||||
}
|
||||
if v.ProposerPriority > max {
|
||||
max = v.ProposerPriority
|
||||
}
|
||||
}
|
||||
diff := max - min
|
||||
if diff < 0 {
|
||||
return -1 * diff
|
||||
} else {
|
||||
return diff
|
||||
}
|
||||
}
|
||||
|
||||
func (vals *ValidatorSet) getValWithMostPriority() *Validator {
|
||||
var res *Validator
|
||||
for _, val := range vals.Validators {
|
||||
res = res.Cmp(val)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (vals *ValidatorSet) shiftByAvgProposerPriority() {
|
||||
if vals.IsNilOrEmpty() {
|
||||
panic("empty validator set")
|
||||
}
|
||||
avgProposerPriority := vals.computeAvgProposerPriority()
|
||||
for _, val := range vals.Validators {
|
||||
val.ProposerPriority = safeSubClip(val.ProposerPriority, avgProposerPriority)
|
||||
}
|
||||
}
|
||||
|
||||
// Makes a copy of the validator list.
|
||||
func validatorListCopy(valsList []*Validator) []*Validator {
|
||||
if valsList == nil {
|
||||
return nil
|
||||
}
|
||||
valsCopy := make([]*Validator, len(valsList))
|
||||
for i, val := range valsList {
|
||||
valsCopy[i] = val.Copy()
|
||||
}
|
||||
return valsCopy
|
||||
}
|
||||
|
||||
// Copy each validator into a new ValidatorSet.
|
||||
func (vals *ValidatorSet) Copy() *ValidatorSet {
|
||||
return &ValidatorSet{
|
||||
Validators: validatorListCopy(vals.Validators),
|
||||
Proposer: vals.Proposer,
|
||||
totalVotingPower: vals.totalVotingPower,
|
||||
}
|
||||
}
|
||||
|
||||
// HasAddress returns true if address given is in the validator set, false -
|
||||
// otherwise.
|
||||
func (vals *ValidatorSet) HasAddress(address []byte) bool {
|
||||
idx := sort.Search(len(vals.Validators), func(i int) bool {
|
||||
return bytes.Compare(address, vals.Validators[i].Address.Bytes()) <= 0
|
||||
})
|
||||
return idx < len(vals.Validators) && bytes.Equal(vals.Validators[idx].Address.Bytes(), address)
|
||||
}
|
||||
|
||||
// GetByAddress returns an index of the validator with address and validator
|
||||
// itself if found. Otherwise, -1 and nil are returned.
|
||||
func (vals *ValidatorSet) GetByAddress(address common.Address) (index int, val *Validator) {
|
||||
idx := sort.Search(len(vals.Validators), func(i int) bool {
|
||||
return bytes.Compare(address.Bytes(), vals.Validators[i].Address.Bytes()) <= 0
|
||||
})
|
||||
if idx < len(vals.Validators) && bytes.Equal(vals.Validators[idx].Address.Bytes(), address.Bytes()) {
|
||||
return idx, vals.Validators[idx].Copy()
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// GetByIndex returns the validator's address and validator itself by index.
|
||||
// It returns nil values if index is less than 0 or greater or equal to
|
||||
// len(ValidatorSet.Validators).
|
||||
func (vals *ValidatorSet) GetByIndex(index int) (address []byte, val *Validator) {
|
||||
if index < 0 || index >= len(vals.Validators) {
|
||||
return nil, nil
|
||||
}
|
||||
val = vals.Validators[index]
|
||||
return val.Address.Bytes(), val.Copy()
|
||||
}
|
||||
|
||||
// Size returns the length of the validator set.
|
||||
func (vals *ValidatorSet) Size() int {
|
||||
return len(vals.Validators)
|
||||
}
|
||||
|
||||
// Force recalculation of the set's total voting power.
|
||||
func (vals *ValidatorSet) updateTotalVotingPower() error {
|
||||
|
||||
sum := int64(0)
|
||||
for _, val := range vals.Validators {
|
||||
// mind overflow
|
||||
sum = safeAddClip(sum, val.VotingPower)
|
||||
if sum > MaxTotalVotingPower {
|
||||
return &TotalVotingPowerExceededError{sum, vals.Validators}
|
||||
}
|
||||
}
|
||||
vals.totalVotingPower = sum
|
||||
return nil
|
||||
}
|
||||
|
||||
// TotalVotingPower returns the sum of the voting powers of all validators.
|
||||
// It recomputes the total voting power if required.
|
||||
func (vals *ValidatorSet) TotalVotingPower() int64 {
|
||||
if vals.totalVotingPower == 0 {
|
||||
log.Info("invoking updateTotalVotingPower before returning it")
|
||||
if err := vals.updateTotalVotingPower(); err != nil {
|
||||
// Can/should we do better?
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return vals.totalVotingPower
|
||||
}
|
||||
|
||||
// GetProposer returns the current proposer. If the validator set is empty, nil
|
||||
// is returned.
|
||||
func (vals *ValidatorSet) GetProposer() (proposer *Validator) {
|
||||
if len(vals.Validators) == 0 {
|
||||
return nil
|
||||
}
|
||||
if vals.Proposer == nil {
|
||||
vals.Proposer = vals.findProposer()
|
||||
}
|
||||
return vals.Proposer.Copy()
|
||||
}
|
||||
|
||||
func (vals *ValidatorSet) findProposer() *Validator {
|
||||
var proposer *Validator
|
||||
for _, val := range vals.Validators {
|
||||
if proposer == nil || !bytes.Equal(val.Address.Bytes(), proposer.Address.Bytes()) {
|
||||
proposer = proposer.Cmp(val)
|
||||
}
|
||||
}
|
||||
return proposer
|
||||
}
|
||||
|
||||
// Hash returns the Merkle root hash build using validators (as leaves) in the
|
||||
// set.
|
||||
// func (vals *ValidatorSet) Hash() []byte {
|
||||
// if len(vals.Validators) == 0 {
|
||||
// return nil
|
||||
// }
|
||||
// bzs := make([][]byte, len(vals.Validators))
|
||||
// for i, val := range vals.Validators {
|
||||
// bzs[i] = val.Bytes()
|
||||
// }
|
||||
// return merkle.SimpleHashFromByteSlices(bzs)
|
||||
// }
|
||||
|
||||
// Iterate will run the given function over the set.
|
||||
func (vals *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
|
||||
for i, val := range vals.Validators {
|
||||
stop := fn(i, val.Copy())
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks changes against duplicates, splits the changes in updates and removals, sorts them by address.
|
||||
//
|
||||
// Returns:
|
||||
// updates, removals - the sorted lists of updates and removals
|
||||
// err - non-nil if duplicate entries or entries with negative voting power are seen
|
||||
//
|
||||
// No changes are made to 'origChanges'.
|
||||
func processChanges(origChanges []*Validator) (updates, removals []*Validator, err error) {
|
||||
// Make a deep copy of the changes and sort by address.
|
||||
changes := validatorListCopy(origChanges)
|
||||
sort.Sort(ValidatorsByAddress(changes))
|
||||
|
||||
removals = make([]*Validator, 0, len(changes))
|
||||
updates = make([]*Validator, 0, len(changes))
|
||||
var prevAddr common.Address
|
||||
|
||||
// Scan changes by address and append valid validators to updates or removals lists.
|
||||
for _, valUpdate := range changes {
|
||||
if bytes.Equal(valUpdate.Address.Bytes(), prevAddr.Bytes()) {
|
||||
err = fmt.Errorf("duplicate entry %v in %v", valUpdate, changes)
|
||||
return nil, nil, err
|
||||
}
|
||||
if valUpdate.VotingPower < 0 {
|
||||
err = fmt.Errorf("voting power can't be negative: %v", valUpdate)
|
||||
return nil, nil, err
|
||||
}
|
||||
if valUpdate.VotingPower > MaxTotalVotingPower {
|
||||
err = fmt.Errorf("to prevent clipping/ overflow, voting power can't be higher than %v: %v ",
|
||||
MaxTotalVotingPower, valUpdate)
|
||||
return nil, nil, err
|
||||
}
|
||||
if valUpdate.VotingPower == 0 {
|
||||
removals = append(removals, valUpdate)
|
||||
} else {
|
||||
updates = append(updates, valUpdate)
|
||||
}
|
||||
prevAddr = valUpdate.Address
|
||||
}
|
||||
return updates, removals, err
|
||||
}
|
||||
|
||||
// Verifies a list of updates against a validator set, making sure the allowed
|
||||
// total voting power would not be exceeded if these updates would be applied to the set.
|
||||
//
|
||||
// Returns:
|
||||
// updatedTotalVotingPower - the new total voting power if these updates would be applied
|
||||
// numNewValidators - number of new validators
|
||||
// err - non-nil if the maximum allowed total voting power would be exceeded
|
||||
//
|
||||
// 'updates' should be a list of proper validator changes, i.e. they have been verified
|
||||
// by processChanges for duplicates and invalid values.
|
||||
// No changes are made to the validator set 'vals'.
|
||||
func verifyUpdates(updates []*Validator, vals *ValidatorSet) (updatedTotalVotingPower int64, numNewValidators int, err error) {
|
||||
|
||||
updatedTotalVotingPower = vals.TotalVotingPower()
|
||||
|
||||
for _, valUpdate := range updates {
|
||||
address := valUpdate.Address
|
||||
_, val := vals.GetByAddress(address)
|
||||
if val == nil {
|
||||
// New validator, add its voting power the the total.
|
||||
updatedTotalVotingPower += valUpdate.VotingPower
|
||||
numNewValidators++
|
||||
} else {
|
||||
// Updated validator, add the difference in power to the total.
|
||||
updatedTotalVotingPower += valUpdate.VotingPower - val.VotingPower
|
||||
}
|
||||
overflow := updatedTotalVotingPower > MaxTotalVotingPower
|
||||
if overflow {
|
||||
err = fmt.Errorf(
|
||||
"failed to add/update validator %v, total voting power would exceed the max allowed %v",
|
||||
valUpdate, MaxTotalVotingPower)
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return updatedTotalVotingPower, numNewValidators, nil
|
||||
}
|
||||
|
||||
// Computes the proposer priority for the validators not present in the set based on 'updatedTotalVotingPower'.
|
||||
// Leaves unchanged the priorities of validators that are changed.
|
||||
//
|
||||
// 'updates' parameter must be a list of unique validators to be added or updated.
|
||||
// No changes are made to the validator set 'vals'.
|
||||
func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotalVotingPower int64) {
|
||||
|
||||
for _, valUpdate := range updates {
|
||||
address := valUpdate.Address
|
||||
_, val := vals.GetByAddress(address)
|
||||
if val == nil {
|
||||
// add val
|
||||
// Set ProposerPriority to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't
|
||||
// un-bond and then re-bond to reset their (potentially previously negative) ProposerPriority to zero.
|
||||
//
|
||||
// Contract: updatedVotingPower < MaxTotalVotingPower to ensure ProposerPriority does
|
||||
// not exceed the bounds of int64.
|
||||
//
|
||||
// Compute ProposerPriority = -1.125*totalVotingPower == -(updatedVotingPower + (updatedVotingPower >> 3)).
|
||||
valUpdate.ProposerPriority = -(updatedTotalVotingPower + (updatedTotalVotingPower >> 3))
|
||||
} else {
|
||||
valUpdate.ProposerPriority = val.ProposerPriority
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Merges the vals' validator list with the updates list.
|
||||
// When two elements with same address are seen, the one from updates is selected.
|
||||
// Expects updates to be a list of updates sorted by address with no duplicates or errors,
|
||||
// must have been validated with verifyUpdates() and priorities computed with computeNewPriorities().
|
||||
func (vals *ValidatorSet) applyUpdates(updates []*Validator) {
|
||||
|
||||
existing := vals.Validators
|
||||
merged := make([]*Validator, len(existing)+len(updates))
|
||||
i := 0
|
||||
|
||||
for len(existing) > 0 && len(updates) > 0 {
|
||||
if bytes.Compare(existing[0].Address.Bytes(), updates[0].Address.Bytes()) < 0 { // unchanged validator
|
||||
merged[i] = existing[0]
|
||||
existing = existing[1:]
|
||||
} else {
|
||||
// Apply add or update.
|
||||
merged[i] = updates[0]
|
||||
if bytes.Equal(existing[0].Address.Bytes(), updates[0].Address.Bytes()) {
|
||||
// Validator is present in both, advance existing.
|
||||
existing = existing[1:]
|
||||
}
|
||||
updates = updates[1:]
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// Add the elements which are left.
|
||||
for j := 0; j < len(existing); j++ {
|
||||
merged[i] = existing[j]
|
||||
i++
|
||||
}
|
||||
// OR add updates which are left.
|
||||
for j := 0; j < len(updates); j++ {
|
||||
merged[i] = updates[j]
|
||||
i++
|
||||
}
|
||||
|
||||
vals.Validators = merged[:i]
|
||||
}
|
||||
|
||||
// Checks that the validators to be removed are part of the validator set.
|
||||
// No changes are made to the validator set 'vals'.
|
||||
func verifyRemovals(deletes []*Validator, vals *ValidatorSet) error {
|
||||
|
||||
for _, valUpdate := range deletes {
|
||||
address := valUpdate.Address
|
||||
_, val := vals.GetByAddress(address)
|
||||
if val == nil {
|
||||
return fmt.Errorf("failed to find validator %X to remove", address)
|
||||
}
|
||||
}
|
||||
if len(deletes) > len(vals.Validators) {
|
||||
panic("more deletes than validators")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Removes the validators specified in 'deletes' from validator set 'vals'.
|
||||
// Should not fail as verification has been done before.
|
||||
func (vals *ValidatorSet) applyRemovals(deletes []*Validator) {
|
||||
|
||||
existing := vals.Validators
|
||||
|
||||
merged := make([]*Validator, len(existing)-len(deletes))
|
||||
i := 0
|
||||
|
||||
// Loop over deletes until we removed all of them.
|
||||
for len(deletes) > 0 {
|
||||
if bytes.Equal(existing[0].Address.Bytes(), deletes[0].Address.Bytes()) {
|
||||
deletes = deletes[1:]
|
||||
} else { // Leave it in the resulting slice.
|
||||
merged[i] = existing[0]
|
||||
i++
|
||||
}
|
||||
existing = existing[1:]
|
||||
}
|
||||
|
||||
// Add the elements which are left.
|
||||
for j := 0; j < len(existing); j++ {
|
||||
merged[i] = existing[j]
|
||||
i++
|
||||
}
|
||||
|
||||
vals.Validators = merged[:i]
|
||||
}
|
||||
|
||||
// Main function used by UpdateWithChangeSet() and NewValidatorSet().
|
||||
// If 'allowDeletes' is false then delete operations (identified by validators with voting power 0)
|
||||
// are not allowed and will trigger an error if present in 'changes'.
|
||||
// The 'allowDeletes' flag is set to false by NewValidatorSet() and to true by UpdateWithChangeSet().
|
||||
func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes bool) error {
|
||||
|
||||
if len(changes) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for duplicates within changes, split in 'updates' and 'deletes' lists (sorted).
|
||||
updates, deletes, err := processChanges(changes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !allowDeletes && len(deletes) != 0 {
|
||||
return fmt.Errorf("cannot process validators with voting power 0: %v", deletes)
|
||||
}
|
||||
|
||||
// Verify that applying the 'deletes' against 'vals' will not result in error.
|
||||
if err := verifyRemovals(deletes, vals); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify that applying the 'updates' against 'vals' will not result in error.
|
||||
updatedTotalVotingPower, numNewValidators, err := verifyUpdates(updates, vals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check that the resulting set will not be empty.
|
||||
if numNewValidators == 0 && len(vals.Validators) == len(deletes) {
|
||||
return fmt.Errorf("applying the validator changes would result in empty set")
|
||||
}
|
||||
|
||||
// Compute the priorities for updates.
|
||||
computeNewPriorities(updates, vals, updatedTotalVotingPower)
|
||||
|
||||
// Apply updates and removals.
|
||||
vals.applyUpdates(updates)
|
||||
vals.applyRemovals(deletes)
|
||||
|
||||
if err := vals.updateTotalVotingPower(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Scale and center.
|
||||
vals.RescalePriorities(PriorityWindowSizeFactor * vals.TotalVotingPower())
|
||||
vals.shiftByAvgProposerPriority()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateWithChangeSet attempts to update the validator set with 'changes'.
|
||||
// It performs the following steps:
|
||||
// - validates the changes making sure there are no duplicates and splits them in updates and deletes
|
||||
// - verifies that applying the changes will not result in errors
|
||||
// - computes the total voting power BEFORE removals to ensure that in the next steps the priorities
|
||||
// across old and newly added validators are fair
|
||||
// - computes the priorities of new validators against the final set
|
||||
// - applies the updates against the validator set
|
||||
// - applies the removals against the validator set
|
||||
// - performs scaling and centering of priority values
|
||||
// If an error is detected during verification steps, it is returned and the validator set
|
||||
// is not changed.
|
||||
func (vals *ValidatorSet) UpdateWithChangeSet(changes []*Validator) error {
|
||||
return vals.updateWithChangeSet(changes, true)
|
||||
}
|
||||
|
||||
//-----------------
|
||||
// ErrTooMuchChange
|
||||
|
||||
func IsErrTooMuchChange(err error) bool {
|
||||
switch err.(type) {
|
||||
case errTooMuchChange:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type errTooMuchChange struct {
|
||||
got int64
|
||||
needed int64
|
||||
}
|
||||
|
||||
func (e errTooMuchChange) Error() string {
|
||||
return fmt.Sprintf("Invalid commit -- insufficient old voting power: got %v, needed %v", e.got, e.needed)
|
||||
}
|
||||
|
||||
//----------------
|
||||
|
||||
func (vals *ValidatorSet) String() string {
|
||||
return vals.StringIndented("")
|
||||
}
|
||||
|
||||
func (vals *ValidatorSet) StringIndented(indent string) string {
|
||||
if vals == nil {
|
||||
return "nil-ValidatorSet"
|
||||
}
|
||||
var valStrings []string
|
||||
vals.Iterate(func(index int, val *Validator) bool {
|
||||
valStrings = append(valStrings, val.String())
|
||||
return false
|
||||
})
|
||||
return fmt.Sprintf(`ValidatorSet{
|
||||
%s Proposer: %v
|
||||
%s Validators:
|
||||
%s %v
|
||||
%s}`,
|
||||
indent, vals.GetProposer().String(),
|
||||
indent,
|
||||
indent, strings.Join(valStrings, "\n"+indent+" "),
|
||||
indent)
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// Implements sort for sorting validators by address.
|
||||
|
||||
// Sort validators by address.
|
||||
type ValidatorsByAddress []*Validator
|
||||
|
||||
func (valz ValidatorsByAddress) Len() int {
|
||||
return len(valz)
|
||||
}
|
||||
|
||||
func (valz ValidatorsByAddress) Less(i, j int) bool {
|
||||
return bytes.Compare(valz[i].Address.Bytes(), valz[j].Address.Bytes()) == -1
|
||||
}
|
||||
|
||||
func (valz ValidatorsByAddress) Swap(i, j int) {
|
||||
it := valz[i]
|
||||
valz[i] = valz[j]
|
||||
valz[j] = it
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// safe addition/subtraction
|
||||
|
||||
func safeAdd(a, b int64) (int64, bool) {
|
||||
if b > 0 && a > math.MaxInt64-b {
|
||||
return -1, true
|
||||
} else if b < 0 && a < math.MinInt64-b {
|
||||
return -1, true
|
||||
}
|
||||
return a + b, false
|
||||
}
|
||||
|
||||
func safeSub(a, b int64) (int64, bool) {
|
||||
if b > 0 && a < math.MinInt64+b {
|
||||
return -1, true
|
||||
} else if b < 0 && a > math.MaxInt64+b {
|
||||
return -1, true
|
||||
}
|
||||
return a - b, false
|
||||
}
|
||||
|
||||
func safeAddClip(a, b int64) int64 {
|
||||
c, overflow := safeAdd(a, b)
|
||||
if overflow {
|
||||
if b < 0 {
|
||||
return math.MinInt64
|
||||
}
|
||||
return math.MaxInt64
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func safeSubClip(a, b int64) int64 {
|
||||
c, overflow := safeSub(a, b)
|
||||
if overflow {
|
||||
if b > 0 {
|
||||
return math.MinInt64
|
||||
}
|
||||
return math.MaxInt64
|
||||
}
|
||||
return c
|
||||
}
|
||||
11
core/types/state_data.go
Normal file
11
core/types/state_data.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package types
|
||||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
// StateData represents state received from Ethereum Blockchain
|
||||
type StateData struct {
|
||||
Did uint64
|
||||
Contract common.Address
|
||||
Data string
|
||||
TxHash common.Hash
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/bor"
|
||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
|
|
@ -129,7 +130,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) {
|
|||
chainDb: chainDb,
|
||||
eventMux: stack.EventMux(),
|
||||
accountManager: stack.AccountManager(),
|
||||
engine: CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb),
|
||||
engine: nil,
|
||||
closeBloomHandler: make(chan struct{}),
|
||||
networkID: config.NetworkId,
|
||||
gasPrice: config.Miner.GasPrice,
|
||||
|
|
@ -207,6 +208,10 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) {
|
|||
}
|
||||
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
||||
|
||||
// create eth api and set engine
|
||||
ethAPI := ethapi.NewPublicBlockChainAPI(eth.APIBackend)
|
||||
eth.engine = CreateConsensusEngine(stack, chainConfig, config, chainDb, ethAPI)
|
||||
|
||||
eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -240,11 +245,19 @@ func makeExtraData(extra []byte) []byte {
|
|||
}
|
||||
|
||||
// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service
|
||||
func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine {
|
||||
func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, ethConfig *Config, db ethdb.Database, blockchainAPI *ethapi.PublicBlockChainAPI) consensus.Engine {
|
||||
config := ðConfig.Ethash
|
||||
notify := ethConfig.Miner.Notify
|
||||
noverify := ethConfig.Miner.Noverify
|
||||
|
||||
// If proof-of-authority is requested, set it up
|
||||
if chainConfig.Clique != nil {
|
||||
return clique.New(chainConfig.Clique, db)
|
||||
}
|
||||
// If Matic bor consensus is requested, set it up
|
||||
if chainConfig.Bor != nil {
|
||||
return bor.New(chainConfig, db, blockchainAPI, ethConfig.HeimdallURL, ethConfig.WithoutHeimdall)
|
||||
}
|
||||
// Otherwise assume proof-of-work
|
||||
switch config.PowMode {
|
||||
case ethash.ModeFake:
|
||||
|
|
|
|||
|
|
@ -186,4 +186,10 @@ type Config struct {
|
|||
|
||||
// CheckpointOracle is the configuration for checkpoint oracle.
|
||||
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
|
||||
|
||||
// URL to connect to Heimdall node
|
||||
HeimdallURL string
|
||||
|
||||
// No heimdall service
|
||||
WithoutHeimdall bool
|
||||
}
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -59,6 +59,7 @@ require (
|
|||
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca
|
||||
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
|
||||
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208
|
||||
github.com/xsleonard/go-merkle v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de // indirect
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -212,6 +212,8 @@ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:s
|
|||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk=
|
||||
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
|
||||
github.com/xsleonard/go-merkle v1.1.0 h1:fHe1fuhJjGH22ZzVTAH0jqHLhTGhOq3wQjJN+8P0jQg=
|
||||
github.com/xsleonard/go-merkle v1.1.0/go.mod h1:cW4z+UZ/4f2n9IJgIiyDCdYguchoDyDAPmpuOWGxdGg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) {
|
|||
eventMux: stack.EventMux(),
|
||||
reqDist: newRequestDistributor(peers, &mclock.System{}),
|
||||
accountManager: stack.AccountManager(),
|
||||
engine: eth.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb),
|
||||
engine: nil,
|
||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||
bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
|
||||
valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)),
|
||||
|
|
|
|||
|
|
@ -239,16 +239,16 @@ var (
|
|||
//
|
||||
// This configuration is intentionally not using keyed fields to force anyone
|
||||
// adding flags to the config to also have to set these fields.
|
||||
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil}
|
||||
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil}
|
||||
|
||||
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
|
||||
// and accepted by the Ethereum core developers into the Clique consensus.
|
||||
//
|
||||
// This configuration is intentionally not using keyed fields to force anyone
|
||||
// adding flags to the config to also have to set these fields.
|
||||
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
|
||||
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil}
|
||||
|
||||
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil}
|
||||
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil}
|
||||
TestRules = TestChainConfig.Rules(new(big.Int))
|
||||
)
|
||||
|
||||
|
|
@ -326,6 +326,7 @@ type ChainConfig struct {
|
|||
// Various consensus engines
|
||||
Ethash *EthashConfig `json:"ethash,omitempty"`
|
||||
Clique *CliqueConfig `json:"clique,omitempty"`
|
||||
Bor *BorConfig `json:"bor,omitempty"`
|
||||
}
|
||||
|
||||
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
|
||||
|
|
@ -347,6 +348,21 @@ func (c *CliqueConfig) String() string {
|
|||
return "clique"
|
||||
}
|
||||
|
||||
// BorConfig is the consensus engine configs for Matic bor based sealing.
|
||||
type BorConfig struct {
|
||||
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
||||
ProducerDelay uint64 `json:"producerDelay"` // Number of seconds delay between two producer interval
|
||||
Sprint uint64 `json:"sprint"` // Epoch length to proposer
|
||||
BackupMultiplier uint64 `json:"backupMultiplier"` // Backup multiplier to determine the wiggle time
|
||||
ValidatorContract string `json:"validatorContract"` // Validator set contract
|
||||
StateReceiverContract string `json:"stateReceiverContract"` // State receiver contract
|
||||
}
|
||||
|
||||
// String implements the stringer interface, returning the consensus engine details.
|
||||
func (b *BorConfig) String() string {
|
||||
return "bor"
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface.
|
||||
func (c *ChainConfig) String() string {
|
||||
var engine interface{}
|
||||
|
|
|
|||
Loading…
Reference in a new issue