From e5058f94fd0b455cb8556dad47f0d6d9e5633cb3 Mon Sep 17 00:00:00 2001 From: Jaynti Kanani Date: Fri, 13 Nov 2020 09:33:12 +0530 Subject: [PATCH] new: add bor consensus --- Makefile | 13 + accounts/accounts.go | 1 + consensus/bor/api.go | 192 +++ consensus/bor/bor.go | 1305 +++++++++++++++++++++ consensus/bor/clerk.go | 49 + consensus/bor/errors.go | 143 +++ consensus/bor/genesis_contracts_client.go | 103 ++ consensus/bor/merkle.go | 48 + consensus/bor/rest.go | 139 +++ consensus/bor/snapshot.go | 223 ++++ consensus/bor/snapshot_test.go | 124 ++ consensus/bor/span.go | 16 + consensus/bor/validator.go | 154 +++ consensus/bor/validator_set.go | 703 +++++++++++ core/types/state_data.go | 11 + eth/backend.go | 17 +- eth/config.go | 6 + go.mod | 1 + go.sum | 2 + les/client.go | 2 +- params/config.go | 22 +- 21 files changed, 3268 insertions(+), 6 deletions(-) create mode 100644 consensus/bor/api.go create mode 100644 consensus/bor/bor.go create mode 100644 consensus/bor/clerk.go create mode 100644 consensus/bor/errors.go create mode 100644 consensus/bor/genesis_contracts_client.go create mode 100644 consensus/bor/merkle.go create mode 100644 consensus/bor/rest.go create mode 100644 consensus/bor/snapshot.go create mode 100644 consensus/bor/snapshot_test.go create mode 100644 consensus/bor/span.go create mode 100644 consensus/bor/validator.go create mode 100644 consensus/bor/validator_set.go create mode 100644 core/types/state_data.go diff --git a/Makefile b/Makefile index ecee5f1894..48e7f34528 100644 --- a/Makefile +++ b/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 diff --git a/accounts/accounts.go b/accounts/accounts.go index dc85cba174..237ca44b1c 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -39,6 +39,7 @@ const ( MimetypeDataWithValidator = "data/validator" MimetypeTypedData = "data/typed" MimetypeClique = "application/x-clique-header" + MimetypeBor = "application/x-bor-header" MimetypeTextPlain = "text/plain" ) diff --git a/consensus/bor/api.go b/consensus/bor/api.go new file mode 100644 index 0000000000..12841290af --- /dev/null +++ b/consensus/bor/api.go @@ -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) +} diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go new file mode 100644 index 0000000000..6210a6207e --- /dev/null +++ b/consensus/bor/bor.go @@ -0,0 +1,1305 @@ +package bor + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "math/big" + "sort" + "strings" + "sync" + "time" + + lru "github.com/hashicorp/golang-lru" + "golang.org/x/crypto/sha3" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi" + "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/misc" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" +) + +const ( + checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database + inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + inmemorySignatures = 4096 // Number of recent block signatures to keep in memory +) + +// Bor protocol constants. +var ( + defaultSprintLength = uint64(64) // Default number of blocks after which to checkpoint and reset the pending votes + + extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity + extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal + + uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. + + diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures + diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures + + validatorHeaderBytesLength = common.AddressLength + 20 // address + power + systemAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") +) + +// Various error messages to mark blocks invalid. These should be private to +// prevent engine specific errors from being referenced in the remainder of the +// codebase, inherently breaking if the engine is swapped out. Please put common +// error types into the consensus package. +var ( + // errUnknownBlock is returned when the list of signers is requested for a block + // that is not part of the local blockchain. + errUnknownBlock = errors.New("unknown block") + + // errInvalidCheckpointBeneficiary is returned if a checkpoint/epoch transition + // block has a beneficiary set to non-zeroes. + errInvalidCheckpointBeneficiary = errors.New("beneficiary in checkpoint block non-zero") + + // errInvalidVote is returned if a nonce value is something else that the two + // allowed constants of 0x00..0 or 0xff..f. + errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f") + + // errInvalidCheckpointVote is returned if a checkpoint/epoch transition block + // has a vote nonce set to non-zeroes. + errInvalidCheckpointVote = errors.New("vote nonce in checkpoint block non-zero") + + // errMissingVanity is returned if a block's extra-data section is shorter than + // 32 bytes, which is required to store the signer vanity. + errMissingVanity = errors.New("extra-data 32 byte vanity prefix missing") + + // errMissingSignature is returned if a block's extra-data section doesn't seem + // to contain a 65 byte secp256k1 signature. + errMissingSignature = errors.New("extra-data 65 byte signature suffix missing") + + // errExtraValidators is returned if non-sprint-end block contain validator data in + // their extra-data fields. + errExtraValidators = errors.New("non-sprint-end block contains extra validator list") + + // errInvalidSpanValidators is returned if a block contains an + // invalid list of validators (i.e. non divisible by 40 bytes). + errInvalidSpanValidators = errors.New("invalid validator list on sprint end block") + + // errInvalidMixDigest is returned if a block's mix digest is non-zero. + errInvalidMixDigest = errors.New("non-zero mix digest") + + // errInvalidUncleHash is returned if a block contains an non-empty uncle list. + errInvalidUncleHash = errors.New("non empty uncle hash") + + // errInvalidDifficulty is returned if the difficulty of a block neither 1 or 2. + errInvalidDifficulty = errors.New("invalid difficulty") + + // ErrInvalidTimestamp is returned if the timestamp of a block is lower than + // the previous block's timestamp + the minimum block period. + ErrInvalidTimestamp = errors.New("invalid timestamp") + + // errOutOfRangeChain is returned if an authorization list is attempted to + // be modified via out-of-range or non-contiguous headers. + errOutOfRangeChain = errors.New("out of range or non-contiguous chain") +) + +// SignerFn is a signer callback function to request a header to be signed by a +// backing account. +type SignerFn func(accounts.Account, string, []byte) ([]byte, error) + +// ecrecover extracts the Ethereum account address from a signed header. +func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) { + // If the signature's already cached, return that + hash := header.Hash() + if address, known := sigcache.Get(hash); known { + return address.(common.Address), nil + } + // Retrieve the signature from the header extra-data + if len(header.Extra) < extraSeal { + return common.Address{}, errMissingSignature + } + signature := header.Extra[len(header.Extra)-extraSeal:] + + // Recover the public key and the Ethereum address + pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature) + if err != nil { + return common.Address{}, err + } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + + sigcache.Add(hash, signer) + return signer, nil +} + +// SealHash returns the hash of a block prior to it being sealed. +func SealHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewLegacyKeccak256() + encodeSigHeader(hasher, header) + hasher.Sum(hash[:0]) + return hash +} + +func encodeSigHeader(w io.Writer, header *types.Header) { + err := rlp.Encode(w, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short + header.MixDigest, + header.Nonce, + }) + if err != nil { + panic("can't encode: " + err.Error()) + } +} + +// CalcProducerDelay is the block delay algorithm based on block time, period, producerDelay and turn-ness of a signer +func CalcProducerDelay(number uint64, succession int, c *params.BorConfig) uint64 { + // When the block is the first block of the sprint, it is expected to be delayed by `producerDelay`. + // That is to allow time for block propagation in the last sprint + delay := c.Period + if number%c.Sprint == 0 { + delay = c.ProducerDelay + } + if succession > 0 { + delay += uint64(succession) * c.BackupMultiplier + } + return delay +} + +// BorRLP returns the rlp bytes which needs to be signed for the bor +// sealing. The RLP to sign consists of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// Note, the method requires the extra data to be at least 65 bytes, otherwise it +// panics. This is done to avoid accidentally using both forms (signature present +// or not), which could be abused to produce different hashes for the same header. +func BorRLP(header *types.Header) []byte { + b := new(bytes.Buffer) + encodeSigHeader(b, header) + return b.Bytes() +} + +// Bor is the matic-bor consensus engine +type Bor struct { + chainConfig *params.ChainConfig // Chain config + config *params.BorConfig // Consensus engine configuration parameters for bor consensus + db ethdb.Database // Database to store and retrieve snapshot checkpoints + + recents *lru.ARCCache // Snapshots for recent block to speed up reorgs + signatures *lru.ARCCache // Signatures of recent blocks to speed up mining + + signer common.Address // Ethereum address of the signing key + signFn SignerFn // Signer function to authorize hashes with + lock sync.RWMutex // Protects the signer fields + + ethAPI *ethapi.PublicBlockChainAPI + GenesisContractsClient *GenesisContractsClient + validatorSetABI abi.ABI + stateReceiverABI abi.ABI + HeimdallClient IHeimdallClient + WithoutHeimdall bool + + stateSyncFeed event.Feed + scope event.SubscriptionScope + // The fields below are for testing only + fakeDiff bool // Skip difficulty verifications +} + +// New creates a Matic Bor consensus engine. +func New( + chainConfig *params.ChainConfig, + db ethdb.Database, + ethAPI *ethapi.PublicBlockChainAPI, + heimdallURL string, + withoutHeimdall bool, +) *Bor { + // get bor config + borConfig := chainConfig.Bor + + // Set any missing consensus parameters to their defaults + if borConfig != nil && borConfig.Sprint == 0 { + borConfig.Sprint = defaultSprintLength + } + + // Allocate the snapshot caches and create the engine + recents, _ := lru.NewARC(inmemorySnapshots) + signatures, _ := lru.NewARC(inmemorySignatures) + vABI, _ := abi.JSON(strings.NewReader(validatorsetABI)) + sABI, _ := abi.JSON(strings.NewReader(stateReceiverABI)) + heimdallClient, _ := NewHeimdallClient(heimdallURL) + genesisContractsClient := NewGenesisContractsClient(chainConfig, borConfig.ValidatorContract, borConfig.StateReceiverContract, ethAPI) + c := &Bor{ + chainConfig: chainConfig, + config: borConfig, + db: db, + ethAPI: ethAPI, + recents: recents, + signatures: signatures, + validatorSetABI: vABI, + stateReceiverABI: sABI, + GenesisContractsClient: genesisContractsClient, + HeimdallClient: heimdallClient, + WithoutHeimdall: withoutHeimdall, + } + + return c +} + +// Author implements consensus.Engine, returning the Ethereum address recovered +// from the signature in the header's extra-data section. +func (c *Bor) Author(header *types.Header) (common.Address, error) { + return ecrecover(header, c.signatures) +} + +// VerifyHeader checks whether a header conforms to the consensus rules. +func (c *Bor) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error { + return c.verifyHeader(chain, header, nil) +} + +// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The +// method returns a quit channel to abort the operations and a results channel to +// retrieve the async verifications (the order is that of the input slice). +func (c *Bor) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { + abort := make(chan struct{}) + results := make(chan error, len(headers)) + + go func() { + for i, header := range headers { + err := c.verifyHeader(chain, header, headers[:i]) + + select { + case <-abort: + return + case results <- err: + } + } + }() + return abort, results +} + +// verifyHeader checks whether a header conforms to the consensus rules.The +// caller may optionally pass in a batch of parents (ascending order) to avoid +// looking those up from the database. This is useful for concurrently verifying +// a batch of new headers. +func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { + if header.Number == nil { + return errUnknownBlock + } + number := header.Number.Uint64() + + // Don't waste time checking blocks from the future + if header.Time > uint64(time.Now().Unix()) { + return consensus.ErrFutureBlock + } + + if err := validateHeaderExtraField(header.Extra); err != nil { + return err + } + + // check extr adata + isSprintEnd := (number+1)%c.config.Sprint == 0 + + // Ensure that the extra-data contains a signer list on checkpoint, but none otherwise + signersBytes := len(header.Extra) - extraVanity - extraSeal + if !isSprintEnd && signersBytes != 0 { + return errExtraValidators + } + if isSprintEnd && signersBytes%validatorHeaderBytesLength != 0 { + return errInvalidSpanValidators + } + // Ensure that the mix digest is zero as we don't have fork protection currently + if header.MixDigest != (common.Hash{}) { + return errInvalidMixDigest + } + // Ensure that the block doesn't contain any uncles which are meaningless in PoA + if header.UncleHash != uncleHash { + return errInvalidUncleHash + } + // Ensure that the block's difficulty is meaningful (may not be correct at this point) + if number > 0 { + if header.Difficulty == nil { + return errInvalidDifficulty + } + } + // If all checks passed, validate any special fields for hard forks + if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil { + return err + } + // All basic checks passed, verify cascading fields + return c.verifyCascadingFields(chain, header, parents) +} + +// validateHeaderExtraField validates that the extra-data contains both the vanity and signature. +// header.Extra = header.Vanity + header.ProducerBytes (optional) + header.Seal +func validateHeaderExtraField(extraBytes []byte) error { + if len(extraBytes) < extraVanity { + return errMissingVanity + } + if len(extraBytes) < extraVanity+extraSeal { + return errMissingSignature + } + return nil +} + +// verifyCascadingFields verifies all the header fields that are not standalone, +// rather depend on a batch of previous headers. The caller may optionally pass +// in a batch of parents (ascending order) to avoid looking those up from the +// database. This is useful for concurrently verifying a batch of new headers. +func (c *Bor) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { + // The genesis block is the always valid dead-end + number := header.Number.Uint64() + if number == 0 { + return nil + } + + // Ensure that the block's timestamp isn't too close to it's parent + var parent *types.Header + if len(parents) > 0 { + parent = parents[len(parents)-1] + } else { + parent = chain.GetHeader(header.ParentHash, number-1) + } + + if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { + return consensus.ErrUnknownAncestor + } + + if parent.Time+c.config.Period > header.Time { + return ErrInvalidTimestamp + } + + // Retrieve the snapshot needed to verify this header and cache it + snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + + // verify the validator list in the last sprint block + if isSprintStart(number, c.config.Sprint) { + parentValidatorBytes := parent.Extra[extraVanity : len(parent.Extra)-extraSeal] + validatorsBytes := make([]byte, len(snap.ValidatorSet.Validators)*validatorHeaderBytesLength) + + currentValidators := snap.ValidatorSet.Copy().Validators + // sort validator by address + sort.Sort(ValidatorsByAddress(currentValidators)) + for i, validator := range currentValidators { + copy(validatorsBytes[i*validatorHeaderBytesLength:], validator.HeaderBytes()) + } + // len(header.Extra) >= extraVanity+extraSeal has already been validated in validateHeaderExtraField, so this won't result in a panic + if !bytes.Equal(parentValidatorBytes, validatorsBytes) { + return &MismatchingValidatorsError{number - 1, validatorsBytes, parentValidatorBytes} + } + } + + // All basic checks passed, verify the seal and return + return c.verifySeal(chain, header, parents) +} + +// snapshot retrieves the authorization snapshot at a given point in time. +func (c *Bor) snapshot(chain consensus.ChainHeaderReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { + // Search for a snapshot in memory or on disk for checkpoints + var ( + headers []*types.Header + snap *Snapshot + ) + + for snap == nil { + // If an in-memory snapshot was found, use that + if s, ok := c.recents.Get(hash); ok { + snap = s.(*Snapshot) + break + } + + // If an on-disk checkpoint snapshot can be found, use that + if number%checkpointInterval == 0 { + if s, err := loadSnapshot(c.config, c.signatures, c.db, hash, c.ethAPI); err == nil { + log.Trace("Loaded snapshot from disk", "number", number, "hash", hash) + snap = s + break + } + } + + // If we're at the genesis, snapshot the initial state. Alternatively if we're + // at a checkpoint block without a parent (light client CHT), or we have piled + // up more headers than allowed to be reorged (chain reinit from a freezer), + // consider the checkpoint trusted and snapshot it. + // TODO fix this + if number == 0 { + checkpoint := chain.GetHeaderByNumber(number) + if checkpoint != nil { + // get checkpoint data + hash := checkpoint.Hash() + + // get validators and current span + validators, err := c.GetCurrentValidators(number, number+1) + if err != nil { + return nil, err + } + + // new snap shot + snap = newSnapshot(c.config, c.signatures, number, hash, validators, c.ethAPI) + if err := snap.store(c.db); err != nil { + return nil, err + } + log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash) + break + } + } + + // No snapshot for this header, gather the header and move backward + var header *types.Header + if len(parents) > 0 { + // If we have explicit parents, pick from there (enforced) + header = parents[len(parents)-1] + if header.Hash() != hash || header.Number.Uint64() != number { + return nil, consensus.ErrUnknownAncestor + } + parents = parents[:len(parents)-1] + } else { + // No explicit parents (or no more left), reach out to the database + header = chain.GetHeader(hash, number) + if header == nil { + return nil, consensus.ErrUnknownAncestor + } + } + headers = append(headers, header) + number, hash = number-1, header.ParentHash + } + + // check if snapshot is nil + if snap == nil { + return nil, fmt.Errorf("Unknown error while retrieving snapshot at block number %v", number) + } + + // Previous snapshot found, apply any pending headers on top of it + for i := 0; i < len(headers)/2; i++ { + headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i] + } + + snap, err := snap.apply(headers) + if err != nil { + return nil, err + } + c.recents.Add(snap.Hash, snap) + + // If we've generated a new checkpoint snapshot, save to disk + if snap.Number%checkpointInterval == 0 && len(headers) > 0 { + if err = snap.store(c.db); err != nil { + return nil, err + } + log.Trace("Stored snapshot to disk", "number", snap.Number, "hash", snap.Hash) + } + return snap, err +} + +// VerifyUncles implements consensus.Engine, always returning an error for any +// uncles as this consensus mechanism doesn't permit uncles. +func (c *Bor) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + if len(block.Uncles()) > 0 { + return errors.New("uncles not allowed") + } + return nil +} + +// VerifySeal implements consensus.Engine, checking whether the signature contained +// in the header satisfies the consensus protocol requirements. +func (c *Bor) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { + return c.verifySeal(chain, header, nil) +} + +// verifySeal checks whether the signature contained in the header satisfies the +// consensus protocol requirements. The method accepts an optional list of parent +// headers that aren't yet part of the local blockchain to generate the snapshots +// from. +func (c *Bor) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { + // Verifying the genesis block is not supported + number := header.Number.Uint64() + if number == 0 { + return errUnknownBlock + } + // Retrieve the snapshot needed to verify this header and cache it + snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + + // Resolve the authorization key and check against signers + signer, err := ecrecover(header, c.signatures) + if err != nil { + return err + } + if !snap.ValidatorSet.HasAddress(signer.Bytes()) { + // Check the UnauthorizedSignerError.Error() msg to see why we pass number-1 + return &UnauthorizedSignerError{number - 1, signer.Bytes()} + } + + succession, err := snap.GetSignerSuccessionNumber(signer) + if err != nil { + return err + } + + var parent *types.Header + if len(parents) > 0 { // if parents is nil, len(parents) is zero + parent = parents[len(parents)-1] + } else if number > 0 { + parent = chain.GetHeader(header.ParentHash, number-1) + } + + if parent != nil && header.Time < parent.Time+CalcProducerDelay(number, succession, c.config) { + return &BlockTooSoonError{number, succession} + } + + // Ensure that the difficulty corresponds to the turn-ness of the signer + if !c.fakeDiff { + difficulty := snap.Difficulty(signer) + if header.Difficulty.Uint64() != difficulty { + return &WrongDifficultyError{number, difficulty, header.Difficulty.Uint64(), signer.Bytes()} + } + } + + return nil +} + +// Prepare implements consensus.Engine, preparing all the consensus fields of the +// header for running the transactions on top. +func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { + // If the block isn't a checkpoint, cast a random vote (good enough for now) + header.Coinbase = common.Address{} + header.Nonce = types.BlockNonce{} + + number := header.Number.Uint64() + // Assemble the validator snapshot to check which votes make sense + snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return err + } + + // Set the correct difficulty + header.Difficulty = new(big.Int).SetUint64(snap.Difficulty(c.signer)) + + // Ensure the extra data has all it's components + if len(header.Extra) < extraVanity { + header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) + } + header.Extra = header.Extra[:extraVanity] + + // get validator set if number + if (number+1)%c.config.Sprint == 0 { + newValidators, err := c.GetCurrentValidators(snap.Number, number+1) + if err != nil { + return errors.New("unknown validators") + } + + // sort validator by address + sort.Sort(ValidatorsByAddress(newValidators)) + for _, validator := range newValidators { + header.Extra = append(header.Extra, validator.HeaderBytes()...) + } + } + + // add extra seal space + header.Extra = append(header.Extra, make([]byte, extraSeal)...) + + // Mix digest is reserved for now, set to empty + header.MixDigest = common.Hash{} + + // Ensure the timestamp has the correct delay + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + + var succession int + // if signer is not empty + if bytes.Compare(c.signer.Bytes(), common.Address{}.Bytes()) != 0 { + succession, err = snap.GetSignerSuccessionNumber(c.signer) + if err != nil { + return err + } + } + + header.Time = parent.Time + CalcProducerDelay(number, succession, c.config) + if header.Time < uint64(time.Now().Unix()) { + header.Time = uint64(time.Now().Unix()) + } + return nil +} + +// Finalize implements consensus.Engine, ensuring no uncles are set, nor block +// rewards given. +func (c *Bor) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { + var err error + headerNumber := header.Number.Uint64() + if headerNumber%c.config.Sprint == 0 { + cx := chainContext{Chain: chain, Bor: c} + // check and commit span + if err := c.checkAndCommitSpan(state, header, cx); err != nil { + log.Error("Error while committing span", "error", err) + return + } + + if !c.WithoutHeimdall { + // commit statees + _, err = c.CommitStates(state, header, cx) + if err != nil { + log.Error("Error while committing states", "error", err) + return + } + } + } + + // No block rewards in PoA, so the state remains as is and uncles are dropped + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + header.UncleHash = types.CalcUncleHash(nil) +} + +// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, +// nor block rewards given, and returns the final block. +func (c *Bor) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { + headerNumber := header.Number.Uint64() + if headerNumber%c.config.Sprint == 0 { + cx := chainContext{Chain: chain, Bor: c} + + // check and commit span + err := c.checkAndCommitSpan(state, header, cx) + if err != nil { + log.Error("Error while committing span", "error", err) + return nil, err + } + + if !c.WithoutHeimdall { + // commit states + _, err = c.CommitStates(state, header, cx) + if err != nil { + log.Error("Error while committing states", "error", err) + return nil, err + } + } + } + + // No block rewards in PoA, so the state remains as is and uncles are dropped + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + header.UncleHash = types.CalcUncleHash(nil) + + // Assemble block + block := types.NewBlock(header, txs, nil, receipts, new(trie.Trie)) + // return the final block for sealing + return block, nil +} + +// Authorize injects a private key into the consensus engine to mint new blocks +// with. +func (c *Bor) Authorize(signer common.Address, signFn SignerFn) { + c.lock.Lock() + defer c.lock.Unlock() + + c.signer = signer + c.signFn = signFn +} + +// Seal implements consensus.Engine, attempting to create a sealed block using +// the local signing credentials. +func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { + header := block.Header() + // Sealing the genesis block is not supported + number := header.Number.Uint64() + if number == 0 { + return errUnknownBlock + } + // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) + if c.config.Period == 0 && len(block.Transactions()) == 0 { + log.Info("Sealing paused, waiting for transactions") + return nil + } + // Don't hold the signer fields for the entire sealing procedure + c.lock.RLock() + signer, signFn := c.signer, c.signFn + c.lock.RUnlock() + + snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return err + } + + // Bail out if we're unauthorized to sign a block + if !snap.ValidatorSet.HasAddress(signer.Bytes()) { + // Check the UnauthorizedSignerError.Error() msg to see why we pass number-1 + return &UnauthorizedSignerError{number - 1, signer.Bytes()} + } + + successionNumber, err := snap.GetSignerSuccessionNumber(signer) + if err != nil { + return err + } + + // Sweet, the protocol permits us to sign the block, wait for our time + delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple + // wiggle was already accounted for in header.Time, this is just for logging + wiggle := time.Duration(successionNumber) * time.Duration(c.config.BackupMultiplier) * time.Second + + // Sign all the things! + sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeBor, BorRLP(header)) + if err != nil { + return err + } + copy(header.Extra[len(header.Extra)-extraSeal:], sighash) + + // Wait until sealing is terminated or delay timeout. + log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) + go func() { + select { + case <-stop: + log.Debug("Discarding sealing operation for block", "number", number) + return + case <-time.After(delay): + if wiggle > 0 { + log.Info( + "Sealing out-of-turn", + "number", number, + "wiggle", common.PrettyDuration(wiggle), + "in-turn-signer", snap.ValidatorSet.GetProposer().Address.Hex(), + ) + } + log.Info( + "Sealing successful", + "number", number, + "delay", delay, + "headerDifficulty", header.Difficulty, + ) + } + select { + case results <- block.WithSeal(header): + default: + log.Warn("Sealing result was not read by miner", "number", number, "sealhash", SealHash(header)) + } + }() + return nil +} + +// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty +// that a new block should have based on the previous blocks in the chain and the +// current signer. +func (c *Bor) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil) + if err != nil { + return nil + } + return new(big.Int).SetUint64(snap.Difficulty(c.signer)) +} + +// SealHash returns the hash of a block prior to it being sealed. +func (c *Bor) SealHash(header *types.Header) common.Hash { + return SealHash(header) +} + +// APIs implements consensus.Engine, returning the user facing RPC API to allow +// controlling the signer voting. +func (c *Bor) APIs(chain consensus.ChainHeaderReader) []rpc.API { + return []rpc.API{{ + Namespace: "bor", + Version: "1.0", + Service: &API{chain: chain, bor: c}, + Public: false, + }} +} + +// Close implements consensus.Engine. It's a noop for bor as there are no background threads. +func (c *Bor) Close() error { + return nil +} + +// GetCurrentSpan get current span from contract +func (c *Bor) GetCurrentSpan(snapshotNumber uint64) (*Span, error) { + // block + blockNr := rpc.BlockNumber(snapshotNumber) + + // method + method := "getCurrentSpan" + + data, err := c.validatorSetABI.Pack(method) + if err != nil { + log.Error("Unable to pack tx for getCurrentSpan", "error", err) + return nil, err + } + + msgData := (hexutil.Bytes)(data) + toAddress := common.HexToAddress(c.config.ValidatorContract) + gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) + result, err := c.ethAPI.Call(context.Background(), ethapi.CallArgs{ + Gas: &gas, + To: &toAddress, + Data: &msgData, + }, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil) + if err != nil { + return nil, err + } + + // span result + ret := new(struct { + Number *big.Int + StartBlock *big.Int + EndBlock *big.Int + }) + if err := c.validatorSetABI.UnpackIntoInterface(ret, method, result); err != nil { + return nil, err + } + + // create new span + span := Span{ + ID: ret.Number.Uint64(), + StartBlock: ret.StartBlock.Uint64(), + EndBlock: ret.EndBlock.Uint64(), + } + + return &span, nil +} + +// GetCurrentValidators get current validators +func (c *Bor) GetCurrentValidators(snapshotNumber uint64, blockNumber uint64) ([]*Validator, error) { + // block + blockNr := rpc.BlockNumber(snapshotNumber) + + // method + method := "getBorValidators" + + data, err := c.validatorSetABI.Pack(method, big.NewInt(0).SetUint64(blockNumber)) + if err != nil { + log.Error("Unable to pack tx for getValidator", "error", err) + return nil, err + } + + // call + msgData := (hexutil.Bytes)(data) + toAddress := common.HexToAddress(c.config.ValidatorContract) + gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) + result, err := c.ethAPI.Call(context.Background(), ethapi.CallArgs{ + Gas: &gas, + To: &toAddress, + Data: &msgData, + }, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil) + if err != nil { + panic(err) + // return nil, err + } + + var ( + ret0 = new([]common.Address) + ret1 = new([]*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + } + + if err := c.validatorSetABI.UnpackIntoInterface(out, method, result); err != nil { + return nil, err + } + + valz := make([]*Validator, len(*ret0)) + for i, a := range *ret0 { + valz[i] = &Validator{ + Address: a, + VotingPower: (*ret1)[i].Int64(), + } + } + + return valz, nil +} + +func (c *Bor) checkAndCommitSpan( + state *state.StateDB, + header *types.Header, + chain core.ChainContext, +) error { + headerNumber := header.Number.Uint64() + span, err := c.GetCurrentSpan(headerNumber - 1) + if err != nil { + return err + } + if c.needToCommitSpan(span, headerNumber) { + err := c.fetchAndCommitSpan(span.ID+1, state, header, chain) + return err + } + return nil +} + +func (c *Bor) needToCommitSpan(span *Span, headerNumber uint64) bool { + // if span is nil + if span == nil { + return false + } + + // check span is not set initially + if span.EndBlock == 0 { + return true + } + + // if current block is first block of last sprint in current span + if span.EndBlock > c.config.Sprint && span.EndBlock-c.config.Sprint+1 == headerNumber { + return true + } + + return false +} + +func (c *Bor) fetchAndCommitSpan( + newSpanID uint64, + state *state.StateDB, + header *types.Header, + chain core.ChainContext, +) error { + var heimdallSpan HeimdallSpan + + if c.WithoutHeimdall { + s, err := c.getNextHeimdallSpanForTest(newSpanID, state, header, chain) + if err != nil { + return err + } + heimdallSpan = *s + } else { + response, err := c.HeimdallClient.FetchWithRetry(fmt.Sprintf("bor/span/%d", newSpanID), "") + if err != nil { + return err + } + + if err := json.Unmarshal(response.Result, &heimdallSpan); err != nil { + return err + } + } + + // check if chain id matches with heimdall span + if heimdallSpan.ChainID != c.chainConfig.ChainID.String() { + return fmt.Errorf( + "Chain id proposed span, %s, and bor chain id, %s, doesn't match", + heimdallSpan.ChainID, + c.chainConfig.ChainID, + ) + } + + // get validators bytes + var validators []MinimalVal + for _, val := range heimdallSpan.ValidatorSet.Validators { + validators = append(validators, val.MinimalVal()) + } + validatorBytes, err := rlp.EncodeToBytes(validators) + if err != nil { + return err + } + + // get producers bytes + var producers []MinimalVal + for _, val := range heimdallSpan.SelectedProducers { + producers = append(producers, val.MinimalVal()) + } + producerBytes, err := rlp.EncodeToBytes(producers) + if err != nil { + return err + } + + // method + method := "commitSpan" + log.Info("✅ Committing new span", + "id", heimdallSpan.ID, + "startBlock", heimdallSpan.StartBlock, + "endBlock", heimdallSpan.EndBlock, + "validatorBytes", hex.EncodeToString(validatorBytes), + "producerBytes", hex.EncodeToString(producerBytes), + ) + + // get packed data + data, err := c.validatorSetABI.Pack(method, + big.NewInt(0).SetUint64(heimdallSpan.ID), + big.NewInt(0).SetUint64(heimdallSpan.StartBlock), + big.NewInt(0).SetUint64(heimdallSpan.EndBlock), + validatorBytes, + producerBytes, + ) + if err != nil { + log.Error("Unable to pack tx for commitSpan", "error", err) + return err + } + + // get system message + msg := getSystemMessage(common.HexToAddress(c.config.ValidatorContract), data) + + // apply message + return applyMessage(msg, state, header, c.chainConfig, chain) +} + +// GetPendingStateProposals get pending state proposals +func (c *Bor) GetPendingStateProposals(snapshotNumber uint64) ([]*big.Int, error) { + // block + blockNr := rpc.BlockNumber(snapshotNumber) + + // method + method := "getPendingStates" + + data, err := c.stateReceiverABI.Pack(method) + if err != nil { + log.Error("Unable to pack tx for getPendingStates", "error", err) + return nil, err + } + + msgData := (hexutil.Bytes)(data) + toAddress := common.HexToAddress(c.config.StateReceiverContract) + gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) + result, err := c.ethAPI.Call(context.Background(), ethapi.CallArgs{ + Gas: &gas, + To: &toAddress, + Data: &msgData, + }, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil) + if err != nil { + return nil, err + } + + var ret = new([]*big.Int) + if err = c.stateReceiverABI.UnpackIntoInterface(ret, method, result); err != nil { + return nil, err + } + + return *ret, nil +} + +// CommitStates commit states +func (c *Bor) CommitStates( + state *state.StateDB, + header *types.Header, + chain chainContext, +) ([]*types.StateData, error) { + stateSyncs := make([]*types.StateData, 0) + number := header.Number.Uint64() + _lastStateID, err := c.GenesisContractsClient.LastStateId(number - 1) + if err != nil { + return nil, err + } + + to := time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.Sprint).Time), 0) + lastStateID := _lastStateID.Uint64() + log.Info( + "Fetching state updates from Heimdall", + "fromID", lastStateID+1, + "to", to.Format(time.RFC3339)) + eventRecords, err := c.HeimdallClient.FetchStateSyncEvents(lastStateID+1, to.Unix()) + + chainID := c.chainConfig.ChainID.String() + for _, eventRecord := range eventRecords { + if eventRecord.ID <= lastStateID { + continue + } + if err := validateEventRecord(eventRecord, number, to, lastStateID, chainID); err != nil { + log.Error(err.Error()) + break + } + + stateData := types.StateData{ + Did: eventRecord.ID, + Contract: eventRecord.Contract, + Data: hex.EncodeToString(eventRecord.Data), + TxHash: eventRecord.TxHash, + } + stateSyncs = append(stateSyncs, &stateData) + + if err := c.GenesisContractsClient.CommitState(eventRecord, state, header, chain); err != nil { + return nil, err + } + lastStateID++ + } + return stateSyncs, nil +} + +func validateEventRecord(eventRecord *EventRecordWithTime, number uint64, to time.Time, lastStateID uint64, chainID string) error { + // event id should be sequential and event.Time should lie in the range [from, to) + if lastStateID+1 != eventRecord.ID || eventRecord.ChainID != chainID || !eventRecord.Time.Before(to) { + return &InvalidStateReceivedError{number, lastStateID, &to, eventRecord} + } + return nil +} + +func (c *Bor) SetHeimdallClient(h IHeimdallClient) { + c.HeimdallClient = h +} + +// +// Private methods +// + +func (c *Bor) getNextHeimdallSpanForTest( + newSpanID uint64, + state *state.StateDB, + header *types.Header, + chain core.ChainContext, +) (*HeimdallSpan, error) { + headerNumber := header.Number.Uint64() + span, err := c.GetCurrentSpan(headerNumber - 1) + if err != nil { + return nil, err + } + + // get local chain context object + localContext := chain.(chainContext) + // Retrieve the snapshot needed to verify this header and cache it + snap, err := c.snapshot(localContext.Chain, headerNumber-1, header.ParentHash, nil) + if err != nil { + return nil, err + } + + // new span + span.ID = newSpanID + if span.EndBlock == 0 { + span.StartBlock = 256 + } else { + span.StartBlock = span.EndBlock + 1 + } + span.EndBlock = span.StartBlock + (100 * c.config.Sprint) - 1 + + selectedProducers := make([]Validator, len(snap.ValidatorSet.Validators)) + for i, v := range snap.ValidatorSet.Validators { + selectedProducers[i] = *v + } + heimdallSpan := &HeimdallSpan{ + Span: *span, + ValidatorSet: *snap.ValidatorSet, + SelectedProducers: selectedProducers, + ChainID: c.chainConfig.ChainID.String(), + } + + return heimdallSpan, nil +} + +// +// Chain context +// + +// chain context +type chainContext struct { + Chain consensus.ChainHeaderReader + Bor consensus.Engine +} + +func (c chainContext) Engine() consensus.Engine { + return c.Bor +} + +func (c chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { + return c.Chain.GetHeader(hash, number) +} + +// callmsg implements core.Message to allow passing it as a transaction simulator. +type callmsg struct { + ethereum.CallMsg +} + +func (m callmsg) From() common.Address { return m.CallMsg.From } +func (m callmsg) Nonce() uint64 { return 0 } +func (m callmsg) CheckNonce() bool { return false } +func (m callmsg) To() *common.Address { return m.CallMsg.To } +func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } +func (m callmsg) Value() *big.Int { return m.CallMsg.Value } +func (m callmsg) Data() []byte { return m.CallMsg.Data } + +// get system message +func getSystemMessage(toAddress common.Address, data []byte) callmsg { + return callmsg{ + ethereum.CallMsg{ + From: systemAddress, + Gas: math.MaxUint64 / 2, + GasPrice: big.NewInt(0), + Value: big.NewInt(0), + To: &toAddress, + Data: data, + }, + } +} + +// apply message +func applyMessage( + msg callmsg, + state *state.StateDB, + header *types.Header, + chainConfig *params.ChainConfig, + chainContext core.ChainContext, +) error { + // Create a new context to be used in the EVM environment + context := core.NewEVMContext(msg, header, chainContext, &header.Coinbase) + // Create a new environment which holds all relevant information + // about the transaction and calling mechanisms. + vmenv := vm.NewEVM(context, state, chainConfig, vm.Config{}) + // Apply the transaction to the current state (included in the env) + _, _, err := vmenv.Call( + vm.AccountRef(msg.From()), + *msg.To(), + msg.Data(), + msg.Gas(), + msg.Value(), + ) + // Update the state with pending changes + if err != nil { + state.Finalise(true) + } + + return nil +} + +func validatorContains(a []*Validator, x *Validator) (*Validator, bool) { + for _, n := range a { + if bytes.Compare(n.Address.Bytes(), x.Address.Bytes()) == 0 { + return n, true + } + } + return nil, false +} + +func getUpdatedValidatorSet(oldValidatorSet *ValidatorSet, newVals []*Validator) *ValidatorSet { + v := oldValidatorSet + oldVals := v.Validators + + var changes []*Validator + for _, ov := range oldVals { + if f, ok := validatorContains(newVals, ov); ok { + ov.VotingPower = f.VotingPower + } else { + ov.VotingPower = 0 + } + + changes = append(changes, ov) + } + + for _, nv := range newVals { + if _, ok := validatorContains(changes, nv); !ok { + changes = append(changes, nv) + } + } + + v.UpdateWithChangeSet(changes) + return v +} + +func isSprintStart(number, sprint uint64) bool { + return number%sprint == 0 +} diff --git a/consensus/bor/clerk.go b/consensus/bor/clerk.go new file mode 100644 index 0000000000..d7e6982873 --- /dev/null +++ b/consensus/bor/clerk.go @@ -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, + } +} diff --git a/consensus/bor/errors.go b/consensus/bor/errors.go new file mode 100644 index 0000000000..a1e60d1e21 --- /dev/null +++ b/consensus/bor/errors.go @@ -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, + ) +} diff --git a/consensus/bor/genesis_contracts_client.go b/consensus/bor/genesis_contracts_client.go new file mode 100644 index 0000000000..abe3d02701 --- /dev/null +++ b/consensus/bor/genesis_contracts_client.go @@ -0,0 +1,103 @@ +package bor + +import ( + "context" + "math" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" +) + +type GenesisContractsClient struct { + validatorSetABI abi.ABI + stateReceiverABI abi.ABI + ValidatorContract string + StateReceiverContract string + chainConfig *params.ChainConfig + ethAPI *ethapi.PublicBlockChainAPI +} + +const validatorsetABI = `[{"constant":true,"inputs":[],"name":"SPRINT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SYSTEM_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"CHAIN","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FIRST_END_BLOCK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"producers","outputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ROUND_TYPE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BOR_ID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"spanNumbers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"VOTE_TYPE","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"validators","outputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"spans","outputs":[{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"startBlock","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"endBlock","type":"uint256"}],"name":"NewSpan","type":"event"},{"constant":true,"inputs":[],"name":"currentSprint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"}],"name":"getSpan","outputs":[{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentSpan","outputs":[{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNextSpan","outputs":[{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"number","type":"uint256"}],"name":"getSpanByBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"currentSpanNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"}],"name":"getValidatorsTotalStakeBySpan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"}],"name":"getProducersTotalStakeBySpan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"name":"getValidatorBySigner","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"internalType":"struct BorValidatorSet.Validator","name":"result","type":"tuple"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"name":"isValidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"name":"isProducer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"isCurrentValidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"isCurrentProducer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"number","type":"uint256"}],"name":"getBorValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitialValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"newSpan","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"bytes","name":"validatorBytes","type":"bytes"},{"internalType":"bytes","name":"producerBytes","type":"bytes"}],"name":"commitSpan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"},{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bytes","name":"sigs","type":"bytes"}],"name":"getStakePowerBySigs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"rootHash","type":"bytes32"},{"internalType":"bytes32","name":"leaf","type":"bytes32"},{"internalType":"bytes","name":"proof","type":"bytes"}],"name":"checkMembership","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"d","type":"bytes32"}],"name":"leafNode","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"left","type":"bytes32"},{"internalType":"bytes32","name":"right","type":"bytes32"}],"name":"innerNode","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"pure","type":"function"}]` +const stateReceiverABI = `[{"constant":true,"inputs":[],"name":"SYSTEM_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lastStateId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"syncTime","type":"uint256"},{"internalType":"bytes","name":"recordBytes","type":"bytes"}],"name":"commitState","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]` + +func NewGenesisContractsClient( + chainConfig *params.ChainConfig, + validatorContract, + stateReceiverContract string, + ethAPI *ethapi.PublicBlockChainAPI, +) *GenesisContractsClient { + vABI, _ := abi.JSON(strings.NewReader(validatorsetABI)) + sABI, _ := abi.JSON(strings.NewReader(stateReceiverABI)) + return &GenesisContractsClient{ + validatorSetABI: vABI, + stateReceiverABI: sABI, + ValidatorContract: validatorContract, + StateReceiverContract: stateReceiverContract, + chainConfig: chainConfig, + ethAPI: ethAPI, + } +} + +func (gc *GenesisContractsClient) CommitState( + event *EventRecordWithTime, + state *state.StateDB, + header *types.Header, + chCtx chainContext, +) error { + eventRecord := event.BuildEventRecord() + recordBytes, err := rlp.EncodeToBytes(eventRecord) + if err != nil { + return err + } + method := "commitState" + t := event.Time.Unix() + data, err := gc.stateReceiverABI.Pack(method, big.NewInt(0).SetInt64(t), recordBytes) + if err != nil { + log.Error("Unable to pack tx for commitState", "error", err) + return err + } + log.Info("→ committing new state", "eventRecord", event.String()) + msg := getSystemMessage(common.HexToAddress(gc.StateReceiverContract), data) + if err := applyMessage(msg, state, header, gc.chainConfig, chCtx); err != nil { + return err + } + return nil +} + +func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int, error) { + blockNr := rpc.BlockNumber(snapshotNumber) + method := "lastStateId" + data, err := gc.stateReceiverABI.Pack(method) + if err != nil { + log.Error("Unable to pack tx for LastStateId", "error", err) + return nil, err + } + + msgData := (hexutil.Bytes)(data) + toAddress := common.HexToAddress(gc.StateReceiverContract) + gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) + result, err := gc.ethAPI.Call(context.Background(), ethapi.CallArgs{ + Gas: &gas, + To: &toAddress, + Data: &msgData, + }, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil) + if err != nil { + return nil, err + } + + var ret = new(*big.Int) + if err := gc.stateReceiverABI.UnpackIntoInterface(ret, method, result); err != nil { + return nil, err + } + return *ret, nil +} diff --git a/consensus/bor/merkle.go b/consensus/bor/merkle.go new file mode 100644 index 0000000000..bdfbaba983 --- /dev/null +++ b/consensus/bor/merkle.go @@ -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 +} diff --git a/consensus/bor/rest.go b/consensus/bor/rest.go new file mode 100644 index 0000000000..4408c1c415 --- /dev/null +++ b/consensus/bor/rest.go @@ -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 +} diff --git a/consensus/bor/snapshot.go b/consensus/bor/snapshot.go new file mode 100644 index 0000000000..8405f34fbd --- /dev/null +++ b/consensus/bor/snapshot.go @@ -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)) +} diff --git a/consensus/bor/snapshot_test.go b/consensus/bor/snapshot_test.go new file mode 100644 index 0000000000..6bb8547843 --- /dev/null +++ b/consensus/bor/snapshot_test.go @@ -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) +} diff --git a/consensus/bor/span.go b/consensus/bor/span.go new file mode 100644 index 0000000000..2fd0cf1079 --- /dev/null +++ b/consensus/bor/span.go @@ -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"` +} diff --git a/consensus/bor/validator.go b/consensus/bor/validator.go new file mode 100644 index 0000000000..00e9fdc645 --- /dev/null +++ b/consensus/bor/validator.go @@ -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 +} diff --git a/consensus/bor/validator_set.go b/consensus/bor/validator_set.go new file mode 100644 index 0000000000..0b5c10ebd0 --- /dev/null +++ b/consensus/bor/validator_set.go @@ -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 +} diff --git a/core/types/state_data.go b/core/types/state_data.go new file mode 100644 index 0000000000..38174461c7 --- /dev/null +++ b/core/types/state_data.go @@ -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 +} diff --git a/eth/backend.go b/eth/backend.go index 3fd027137c..13ee1bc765 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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: diff --git a/eth/config.go b/eth/config.go index 0d99c2a3f1..5217d4f6e0 100644 --- a/eth/config.go +++ b/eth/config.go @@ -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 } diff --git a/go.mod b/go.mod index ae1cf64aaf..5a02365520 100755 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 10bec96411..08fd17132c 100755 --- a/go.sum +++ b/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= diff --git a/les/client.go b/les/client.go index a2f7c56dfd..6d51d33ad9 100644 --- a/les/client.go +++ b/les/client.go @@ -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)), diff --git a/params/config.go b/params/config.go index 6cae8cc0b0..d998c94918 100644 --- a/params/config.go +++ b/params/config.go @@ -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{}