diff --git a/cmd/erigon-el/backend/backend.go b/cmd/erigon-el/backend/backend.go index 7cc7276556d..673c2bf19a8 100644 --- a/cmd/erigon-el/backend/backend.go +++ b/cmd/erigon-el/backend/backend.go @@ -411,7 +411,7 @@ func NewBackend(stack *node.Node, config *ethconfig.Config, logger log.Logger) ( } else { consensusConfig = &config.Ethash } - backend.engine = ethconsensusconfig.CreateConsensusEngine(chainConfig, logger, consensusConfig, config.Miner.Notify, config.Miner.Noverify, config.HeimdallURL, config.WithoutHeimdall, stack.DataDir(), allSnapshots, false /* readonly */, backend.chainDB) + backend.engine = ethconsensusconfig.CreateConsensusEngine(chainConfig, logger, consensusConfig, config.Miner.Notify, config.Miner.Noverify, config.HeimdallgRPCAddress, config.HeimdallURL, config.WithoutHeimdall, stack.DataDir(), allSnapshots, false /* readonly */, backend.chainDB) backend.forkValidator = engineapi.NewForkValidator(currentBlockNumber, inMemoryExecution, tmpdir) if err != nil { diff --git a/cmd/integration/commands/flags.go b/cmd/integration/commands/flags.go index 417eada2a4a..337e954ac5f 100644 --- a/cmd/integration/commands/flags.go +++ b/cmd/integration/commands/flags.go @@ -21,6 +21,7 @@ var ( migration string integrityFast, integritySlow bool file string + HeimdallgRPCAddress string HeimdallURL string txtrace bool // Whether to trace the execution (should only be used together with `block`) pruneFlag string diff --git a/cmd/integration/commands/stages.go b/cmd/integration/commands/stages.go index 56643ce9907..174de32bd50 100644 --- a/cmd/integration/commands/stages.go +++ b/cmd/integration/commands/stages.go @@ -1323,5 +1323,5 @@ func initConsensusEngine(cc *chain2.Config, datadir string, db kv.RwDB) (engine } else { consensusConfig = &config.Ethash } - return ethconsensusconfig.CreateConsensusEngine(cc, l, consensusConfig, config.Miner.Notify, config.Miner.Noverify, config.HeimdallURL, config.WithoutHeimdall, datadir, snapshots, db.ReadOnly(), db) + return ethconsensusconfig.CreateConsensusEngine(cc, l, consensusConfig, config.Miner.Notify, config.Miner.Noverify, config.HeimdallgRPCAddress, config.HeimdallURL, config.WithoutHeimdall, datadir, snapshots, db.ReadOnly(), db) } diff --git a/cmd/rpcdaemon/commands/bor_api.go b/cmd/rpcdaemon/commands/bor_api.go index 9bf43905f60..4cf6f295ab2 100644 --- a/cmd/rpcdaemon/commands/bor_api.go +++ b/cmd/rpcdaemon/commands/bor_api.go @@ -4,7 +4,7 @@ import ( "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/kv" - "github.com/ledgerwatch/erigon/consensus/bor" + "github.com/ledgerwatch/erigon/consensus/bor/valset" "github.com/ledgerwatch/erigon/rpc" ) @@ -17,7 +17,7 @@ type BorAPI interface { GetSigners(number *rpc.BlockNumber) ([]common.Address, error) GetSignersAtHash(hash common.Hash) ([]common.Address, error) GetCurrentProposer() (common.Address, error) - GetCurrentValidators() ([]*bor.Validator, error) + GetCurrentValidators() ([]*valset.Validator, error) GetRootHash(start uint64, end uint64) (string, error) } diff --git a/cmd/rpcdaemon/commands/bor_helper.go b/cmd/rpcdaemon/commands/bor_helper.go index f26f707fbe6..dccdc363621 100644 --- a/cmd/rpcdaemon/commands/bor_helper.go +++ b/cmd/rpcdaemon/commands/bor_helper.go @@ -11,6 +11,7 @@ import ( "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/consensus/bor" + "github.com/ledgerwatch/erigon/consensus/bor/valset" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/rpc" @@ -118,7 +119,7 @@ func validateHeaderExtraField(extraBytes []byte) error { } // validatorContains checks for a validator in given validator set -func validatorContains(a []*bor.Validator, x *bor.Validator) (*bor.Validator, bool) { +func validatorContains(a []*valset.Validator, x *valset.Validator) (*valset.Validator, bool) { for _, n := range a { if bytes.Equal(n.Address.Bytes(), x.Address.Bytes()) { return n, true @@ -128,11 +129,11 @@ func validatorContains(a []*bor.Validator, x *bor.Validator) (*bor.Validator, bo } // getUpdatedValidatorSet applies changes to a validator set and returns a new validator set -func getUpdatedValidatorSet(oldValidatorSet *ValidatorSet, newVals []*bor.Validator) *ValidatorSet { +func getUpdatedValidatorSet(oldValidatorSet *ValidatorSet, newVals []*valset.Validator) *ValidatorSet { v := oldValidatorSet oldVals := v.Validators - changes := make([]*bor.Validator, 0, len(oldVals)) + changes := make([]*valset.Validator, 0, len(oldVals)) for _, ov := range oldVals { if f, ok := validatorContains(newVals, ov); ok { ov.VotingPower = f.VotingPower diff --git a/cmd/rpcdaemon/commands/bor_snapshot.go b/cmd/rpcdaemon/commands/bor_snapshot.go index 66b444355e0..1bde2086118 100644 --- a/cmd/rpcdaemon/commands/bor_snapshot.go +++ b/cmd/rpcdaemon/commands/bor_snapshot.go @@ -16,6 +16,7 @@ import ( "github.com/ledgerwatch/erigon/consensus" "github.com/ledgerwatch/erigon/consensus/bor" + "github.com/ledgerwatch/erigon/consensus/bor/valset" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/crypto" @@ -184,10 +185,10 @@ func (api *BorImpl) GetCurrentProposer() (common.Address, error) { } // GetCurrentValidators gets the current validators -func (api *BorImpl) GetCurrentValidators() ([]*bor.Validator, error) { +func (api *BorImpl) GetCurrentValidators() ([]*valset.Validator, error) { snap, err := api.GetSnapshot(nil) if err != nil { - return make([]*bor.Validator, 0), err + return make([]*valset.Validator, 0), err } return snap.ValidatorSet.Validators, nil } @@ -207,11 +208,11 @@ func (api *BorImpl) GetRootHash(start, end uint64) (string, error) { header := rawdb.ReadCurrentHeader(tx) var currentHeaderNumber uint64 = 0 if header == nil { - return "", &bor.InvalidStartEndBlockError{Start: start, End: end, CurrentHeader: currentHeaderNumber} + return "", &valset.InvalidStartEndBlockError{Start: start, End: end, CurrentHeader: currentHeaderNumber} } currentHeaderNumber = header.Number.Uint64() if start > end || end > currentHeaderNumber { - return "", &bor.InvalidStartEndBlockError{Start: start, End: end, CurrentHeader: currentHeaderNumber} + return "", &valset.InvalidStartEndBlockError{Start: start, End: end, CurrentHeader: currentHeaderNumber} } blockHeaders := make([]*types.Header, end-start+1) for number := start; number <= end; number++ { @@ -343,7 +344,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { validatorBytes := header.Extra[extraVanity : len(header.Extra)-extraSeal] // get validators from headers and use that for new validator set - newVals, _ := bor.ParseValidators(validatorBytes) + newVals, _ := valset.ParseValidators(validatorBytes) v := getUpdatedValidatorSet(snap.ValidatorSet.Copy(), newVals) v.IncrementProposerPriority(1) snap.ValidatorSet = v @@ -418,7 +419,7 @@ func loadSnapshot(api *BorImpl, db kv.Tx, borDb kv.Tx, hash common.Hash) (*Snaps snap.config = config.Bor // update total voting power - if err := snap.ValidatorSet.updateTotalVotingPower(); err != nil { + if err := snap.ValidatorSet.UpdateTotalVotingPower(); err != nil { return nil, err } diff --git a/cmd/rpcdaemon/commands/validator_set.go b/cmd/rpcdaemon/commands/validator_set.go index 60db6a75413..8973c987eff 100644 --- a/cmd/rpcdaemon/commands/validator_set.go +++ b/cmd/rpcdaemon/commands/validator_set.go @@ -13,7 +13,7 @@ import ( libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/log/v3" - "github.com/ledgerwatch/erigon/consensus/bor" + "github.com/ledgerwatch/erigon/consensus/bor/valset" ) // MaxTotalVotingPower - the maximum allowed total voting power. @@ -43,8 +43,8 @@ const ( // NOTE: All get/set to validators should copy the value for safety. type ValidatorSet struct { // NOTE: persisted via reflect, must be exported. - Validators []*bor.Validator `json:"validators"` - Proposer *bor.Validator `json:"proposer"` + Validators []*valset.Validator `json:"validators"` + Proposer *valset.Validator `json:"proposer"` // cached (unexported) totalVotingPower int64 @@ -55,7 +55,7 @@ type ValidatorSet struct { // 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 []*bor.Validator) *ValidatorSet { +func NewValidatorSet(valz []*valset.Validator) *ValidatorSet { vals := &ValidatorSet{} err := vals.updateWithChangeSet(valz, false) if err != nil { @@ -97,7 +97,7 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) { vals.RescalePriorities(diffMax) vals.shiftByAvgProposerPriority() - var proposer *bor.Validator + var proposer *valset.Validator // Call IncrementProposerPriority(1) times times. for i := 0; i < times; i++ { proposer = vals.incrementProposerPriority() @@ -129,7 +129,7 @@ func (vals *ValidatorSet) RescalePriorities(diffMax int64) { } } -func (vals *ValidatorSet) incrementProposerPriority() *bor.Validator { +func (vals *ValidatorSet) incrementProposerPriority() *valset.Validator { for _, val := range vals.Validators { // Check for overflow for sum. newPrio := safeAddClip(val.ProposerPriority, val.VotingPower) @@ -182,8 +182,8 @@ func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 { } } -func (vals *ValidatorSet) getValWithMostPriority() *bor.Validator { - var res *bor.Validator +func (vals *ValidatorSet) getValWithMostPriority() *valset.Validator { + var res *valset.Validator for _, val := range vals.Validators { res = res.Cmp(val) } @@ -201,11 +201,11 @@ func (vals *ValidatorSet) shiftByAvgProposerPriority() { } // Makes a copy of the validator list. -func validatorListCopy(valsList []*bor.Validator) []*bor.Validator { +func validatorListCopy(valsList []*valset.Validator) []*valset.Validator { if valsList == nil { return nil } - valsCopy := make([]*bor.Validator, len(valsList)) + valsCopy := make([]*valset.Validator, len(valsList)) for i, val := range valsList { valsCopy[i] = val.Copy() } @@ -232,7 +232,7 @@ func (vals *ValidatorSet) HasAddress(address []byte) bool { // 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 libcommon.Address) (index int, val *bor.Validator) { +func (vals *ValidatorSet) GetByAddress(address libcommon.Address) (index int, val *valset.Validator) { idx := sort.Search(len(vals.Validators), func(i int) bool { return bytes.Compare(address.Bytes(), vals.Validators[i].Address.Bytes()) <= 0 }) @@ -245,7 +245,7 @@ func (vals *ValidatorSet) GetByAddress(address libcommon.Address) (index int, va // 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 *bor.Validator) { +func (vals *ValidatorSet) GetByIndex(index int) (address []byte, val *valset.Validator) { if index < 0 || index >= len(vals.Validators) { return nil, nil } @@ -259,14 +259,14 @@ func (vals *ValidatorSet) Size() int { } // Force recalculation of the set's total voting power. -func (vals *ValidatorSet) updateTotalVotingPower() error { +func (vals *ValidatorSet) UpdateTotalVotingPower() error { sum := int64(0) for _, val := range vals.Validators { // mind overflow sum = safeAddClip(sum, val.VotingPower) if sum > MaxTotalVotingPower { - return &bor.TotalVotingPowerExceededError{Sum: sum, Validators: vals.Validators} + return &valset.TotalVotingPowerExceededError{Sum: sum, Validators: vals.Validators} } } vals.totalVotingPower = sum @@ -278,7 +278,7 @@ func (vals *ValidatorSet) updateTotalVotingPower() error { func (vals *ValidatorSet) TotalVotingPower() int64 { if vals.totalVotingPower == 0 { log.Info("invoking updateTotalVotingPower before returning it") - if err := vals.updateTotalVotingPower(); err != nil { + if err := vals.UpdateTotalVotingPower(); err != nil { // Can/should we do better? panic(err) } @@ -288,7 +288,7 @@ func (vals *ValidatorSet) TotalVotingPower() int64 { // GetProposer returns the current proposer. If the validator set is empty, nil // is returned. -func (vals *ValidatorSet) GetProposer() (proposer *bor.Validator) { +func (vals *ValidatorSet) GetProposer() (proposer *valset.Validator) { if len(vals.Validators) == 0 { return nil } @@ -298,8 +298,8 @@ func (vals *ValidatorSet) GetProposer() (proposer *bor.Validator) { return vals.Proposer.Copy() } -func (vals *ValidatorSet) findProposer() *bor.Validator { - var proposer *bor.Validator +func (vals *ValidatorSet) findProposer() *valset.Validator { + var proposer *valset.Validator for _, val := range vals.Validators { if proposer == nil || !bytes.Equal(val.Address.Bytes(), proposer.Address.Bytes()) { proposer = proposer.Cmp(val) @@ -322,7 +322,7 @@ func (vals *ValidatorSet) findProposer() *bor.Validator { // } // Iterate will run the given function over the set. -func (vals *ValidatorSet) Iterate(fn func(index int, val *bor.Validator) bool) { +func (vals *ValidatorSet) Iterate(fn func(index int, val *valset.Validator) bool) { for i, val := range vals.Validators { stop := fn(i, val.Copy()) if stop { @@ -338,13 +338,13 @@ func (vals *ValidatorSet) Iterate(fn func(index int, val *bor.Validator) bool) { // err - non-nil if duplicate entries or entries with negative voting power are seen // // No changes are made to 'origChanges'. -func processChanges(origChanges []*bor.Validator) (updates, removals []*bor.Validator, err error) { +func processChanges(origChanges []*valset.Validator) (updates, removals []*valset.Validator, err error) { // Make a deep copy of the changes and sort by address. changes := validatorListCopy(origChanges) sort.Sort(ValidatorsByAddress(changes)) - removals = make([]*bor.Validator, 0, len(changes)) - updates = make([]*bor.Validator, 0, len(changes)) + removals = make([]*valset.Validator, 0, len(changes)) + updates = make([]*valset.Validator, 0, len(changes)) var prevAddr libcommon.Address // Scan changes by address and append valid validators to updates or removals lists. @@ -383,7 +383,7 @@ func processChanges(origChanges []*bor.Validator) (updates, removals []*bor.Vali // '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 []*bor.Validator, vals *ValidatorSet) (updatedTotalVotingPower int64, numNewValidators int, err error) { +func verifyUpdates(updates []*valset.Validator, vals *ValidatorSet) (updatedTotalVotingPower int64, numNewValidators int, err error) { updatedTotalVotingPower = vals.TotalVotingPower() @@ -415,7 +415,7 @@ func verifyUpdates(updates []*bor.Validator, vals *ValidatorSet) (updatedTotalVo // // '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 []*bor.Validator, vals *ValidatorSet, updatedTotalVotingPower int64) { +func computeNewPriorities(updates []*valset.Validator, vals *ValidatorSet, updatedTotalVotingPower int64) { for _, valUpdate := range updates { address := valUpdate.Address @@ -441,10 +441,10 @@ func computeNewPriorities(updates []*bor.Validator, vals *ValidatorSet, updatedT // 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 []*bor.Validator) { +func (vals *ValidatorSet) applyUpdates(updates []*valset.Validator) { existing := vals.Validators - merged := make([]*bor.Validator, len(existing)+len(updates)) + merged := make([]*valset.Validator, len(existing)+len(updates)) i := 0 for len(existing) > 0 && len(updates) > 0 { @@ -479,7 +479,7 @@ func (vals *ValidatorSet) applyUpdates(updates []*bor.Validator) { // 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 []*bor.Validator, vals *ValidatorSet) error { +func verifyRemovals(deletes []*valset.Validator, vals *ValidatorSet) error { for _, valUpdate := range deletes { address := valUpdate.Address @@ -496,11 +496,11 @@ func verifyRemovals(deletes []*bor.Validator, vals *ValidatorSet) error { // Removes the validators specified in 'deletes' from validator set 'vals'. // Should not fail as verification has been done before. -func (vals *ValidatorSet) applyRemovals(deletes []*bor.Validator) { +func (vals *ValidatorSet) applyRemovals(deletes []*valset.Validator) { existing := vals.Validators - merged := make([]*bor.Validator, len(existing)-len(deletes)) + merged := make([]*valset.Validator, len(existing)-len(deletes)) i := 0 // Loop over deletes until we removed all of them. @@ -527,7 +527,7 @@ func (vals *ValidatorSet) applyRemovals(deletes []*bor.Validator) { // 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 []*bor.Validator, allowDeletes bool) error { +func (vals *ValidatorSet) updateWithChangeSet(changes []*valset.Validator, allowDeletes bool) error { if len(changes) < 1 { return nil @@ -566,7 +566,7 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*bor.Validator, allowDel vals.applyUpdates(updates) vals.applyRemovals(deletes) - if err := vals.updateTotalVotingPower(); err != nil { + if err := vals.UpdateTotalVotingPower(); err != nil { return err } @@ -590,7 +590,7 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*bor.Validator, allowDel // // If an error is detected during verification steps, it is returned and the validator set // is not changed. -func (vals *ValidatorSet) UpdateWithChangeSet(changes []*bor.Validator) error { +func (vals *ValidatorSet) UpdateWithChangeSet(changes []*valset.Validator) error { return vals.updateWithChangeSet(changes, true) } @@ -626,7 +626,7 @@ func (vals *ValidatorSet) StringIndented(indent string) string { return "nil-ValidatorSet" } var valStrings []string - vals.Iterate(func(index int, val *bor.Validator) bool { + vals.Iterate(func(index int, val *valset.Validator) bool { valStrings = append(valStrings, val.String()) return false }) @@ -646,7 +646,7 @@ func (vals *ValidatorSet) StringIndented(indent string) string { // Implements sort for sorting validators by address. // Sort validators by address. -type ValidatorsByAddress []*bor.Validator +type ValidatorsByAddress []*valset.Validator func (valz ValidatorsByAddress) Len() int { return len(valz) diff --git a/cmd/state/commands/erigon2.go b/cmd/state/commands/erigon2.go index 5fae0f54716..75794f73251 100644 --- a/cmd/state/commands/erigon2.go +++ b/cmd/state/commands/erigon2.go @@ -642,5 +642,5 @@ func initConsensusEngine(cc *chain2.Config, snapshots *snapshotsync.RoSnapshots) } else { consensusConfig = &config.Ethash } - return ethconsensusconfig.CreateConsensusEngine(cc, l, consensusConfig, config.Miner.Notify, config.Miner.Noverify, config.HeimdallURL, config.WithoutHeimdall, datadirCli, snapshots, true /* readonly */) + return ethconsensusconfig.CreateConsensusEngine(cc, l, consensusConfig, config.Miner.Notify, config.Miner.Noverify, config.HeimdallgRPCAddress, config.HeimdallURL, config.WithoutHeimdall, datadirCli, snapshots, true /* readonly */) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cc7936f778a..efeaba026de 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -709,6 +709,13 @@ var ( Usage: "Run without Heimdall service (for testing purpose)", } + // HeimdallgRPCAddressFlag flag for heimdall gRPC address + HeimdallgRPCAddressFlag = cli.StringFlag{ + Name: "bor.heimdallgRPC", + Usage: "Address of Heimdall gRPC service", + Value: "", + } + ConfigFlag = cli.StringFlag{ Name: "config", Usage: "Sets erigon flags from YAML/TOML file", @@ -1366,6 +1373,7 @@ func setParlia(ctx *cli.Context, cfg *chain.ParliaConfig, datadir string) { func setBorConfig(ctx *cli.Context, cfg *ethconfig.Config) { cfg.HeimdallURL = ctx.String(HeimdallURLFlag.Name) cfg.WithoutHeimdall = ctx.Bool(WithoutHeimdallFlag.Name) + cfg.HeimdallgRPCAddress = ctx.String(HeimdallgRPCAddressFlag.Name) } func setMiner(ctx *cli.Context, cfg *params.MiningConfig) { diff --git a/consensus/bor/abi/interface.go b/consensus/bor/abi/interface.go new file mode 100644 index 00000000000..bb05bf0b23f --- /dev/null +++ b/consensus/bor/abi/interface.go @@ -0,0 +1,6 @@ +package abi + +type ABI interface { + Pack(name string, args ...interface{}) ([]byte, error) + UnpackIntoInterface(v interface{}, name string, data []byte) error +} diff --git a/consensus/bor/api.go b/consensus/bor/api.go index c6ca6ea69e5..2296f5f0e55 100644 --- a/consensus/bor/api.go +++ b/consensus/bor/api.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "math" "math/big" + "sort" "strconv" "sync" @@ -13,6 +14,7 @@ import ( "golang.org/x/crypto/sha3" "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/consensus/bor/valset" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/rpc" @@ -44,9 +46,88 @@ func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { if header == nil { return nil, errUnknownBlock } + return api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) } +type BlockSigners struct { + Signers []difficultiesKV + Diff int + Author libcommon.Address +} + +type difficultiesKV struct { + Signer libcommon.Address + Difficulty uint64 +} + +func rankMapDifficulties(values map[libcommon.Address]uint64) []difficultiesKV { + ss := make([]difficultiesKV, 0, len(values)) + for k, v := range values { + ss = append(ss, difficultiesKV{k, v}) + } + + sort.Slice(ss, func(i, j int) bool { + return ss[i].Difficulty > ss[j].Difficulty + }) + + return ss +} + +// GetSnapshotProposerSequence retrieves the in-turn signers of all sprints in a span +func (api *API) GetSnapshotProposerSequence(number *rpc.BlockNumber) (BlockSigners, error) { + snapNumber := *number - 1 + + var difficulties = make(map[libcommon.Address]uint64) + + snap, err := api.GetSnapshot(&snapNumber) + + if err != nil { + return BlockSigners{}, err + } + + proposer := snap.ValidatorSet.GetProposer().Address + proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer) + + signers := snap.signers() + for i := 0; i < len(signers); i++ { + tempIndex := i + if tempIndex < proposerIndex { + tempIndex = tempIndex + len(signers) + } + + difficulties[signers[i]] = uint64(len(signers) - (tempIndex - proposerIndex)) + } + + rankedDifficulties := rankMapDifficulties(difficulties) + + author, err := api.GetAuthor(number) + if err != nil { + return BlockSigners{}, err + } + + diff := int(difficulties[*author]) + blockSigners := &BlockSigners{ + Signers: rankedDifficulties, + Diff: diff, + Author: *author, + } + + return *blockSigners, nil +} + +// GetSnapshotProposer retrieves the in-turn signer at a given block. +func (api *API) GetSnapshotProposer(number *rpc.BlockNumber) (libcommon.Address, error) { + *number -= 1 + snap, err := api.GetSnapshot(number) + + if err != nil { + return libcommon.Address{}, err + } + + return snap.ValidatorSet.GetProposer().Address, nil +} + // GetAuthor retrieves the author a block. func (api *API) GetAuthor(number *rpc.BlockNumber) (*libcommon.Address, error) { // Retrieve the requested block number (or current if none requested) @@ -60,7 +141,9 @@ func (api *API) GetAuthor(number *rpc.BlockNumber) (*libcommon.Address, error) { if header == nil { return nil, errUnknownBlock } + author, err := api.bor.Author(header) + return &author, err } @@ -70,6 +153,7 @@ func (api *API) GetSnapshotAtHash(hash libcommon.Hash) (*Snapshot, error) { if header == nil { return nil, errUnknownBlock } + return api.bor.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) } @@ -86,10 +170,13 @@ func (api *API) GetSigners(number *rpc.BlockNumber) ([]libcommon.Address, error) 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 } @@ -99,10 +186,13 @@ func (api *API) GetSignersAtHash(hash libcommon.Hash) ([]libcommon.Address, erro 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 } @@ -112,15 +202,17 @@ func (api *API) GetCurrentProposer() (libcommon.Address, error) { if err != nil { return libcommon.Address{}, err } + return snap.ValidatorSet.GetProposer().Address, nil } // GetCurrentValidators gets the current validators -func (api *API) GetCurrentValidators() ([]*Validator, error) { +func (api *API) GetCurrentValidators() ([]*valset.Validator, error) { snap, err := api.GetSnapshot(nil) if err != nil { - return make([]*Validator, 0), err + return make([]*valset.Validator, 0), err } + return snap.ValidatorSet.Validators, nil } @@ -129,26 +221,36 @@ 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 := 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} + return "", &valset.InvalidStartEndBlockError{Start: start, End: end, CurrentHeader: 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(number) + <-concurrent wg.Done() }(i) @@ -157,6 +259,7 @@ func (api *API) GetRootHash(start uint64, end uint64) (string, error) { close(concurrent) headers := make([][32]byte, NextPowerOfTwo(length)) + for i := 0; i < len(blockHeaders); i++ { blockHeader := blockHeaders[i] header := crypto.Keccak256(AppendBytes32( @@ -167,6 +270,7 @@ func (api *API) GetRootHash(start uint64, end uint64) (string, error) { )) var arr [32]byte + copy(arr[:], header) headers[i] = arr } @@ -175,8 +279,10 @@ func (api *API) GetRootHash(start uint64, end uint64) (string, error) { 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 } @@ -185,6 +291,7 @@ func (api *API) initializeRootHashCache() error { if api.rootHashCache == nil { api.rootHashCache, err = lru.NewARC(10) } + return err } diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 2a55098b8a7..b0b17cc0686 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -11,7 +11,6 @@ import ( "math/big" "sort" "strconv" - "strings" "sync" "time" @@ -23,9 +22,12 @@ import ( "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/log/v3" - "github.com/ledgerwatch/erigon/accounts/abi" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/consensus/bor/clerk" + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" + "github.com/ledgerwatch/erigon/consensus/bor/statefull" + "github.com/ledgerwatch/erigon/consensus/bor/valset" "github.com/ledgerwatch/erigon/consensus/misc" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/state" @@ -33,9 +35,9 @@ import ( "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/crypto/cryptopool" - "github.com/ledgerwatch/erigon/params/networkname" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/rpc" + "go.uber.org/atomic" ) const ( @@ -115,6 +117,9 @@ var ( // 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") + + errUncleDetected = errors.New("uncles not allowed") + errUnknownValidators = errors.New("unknown validators") ) // SignerFn is a signer callback function to request a header to be signed by a @@ -132,6 +137,7 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache, c *chain.BorConfig) if len(header.Extra) < extraSeal { return libcommon.Address{}, errMissingSignature } + signature := header.Extra[len(header.Extra)-extraSeal:] // Recover the public key and the Ethereum address @@ -143,6 +149,7 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache, c *chain.BorConfig) copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) sigcache.Add(hash, signer) + return signer, nil } @@ -153,6 +160,7 @@ func SealHash(header *types.Header, c *chain.BorConfig) (hash libcommon.Hash) { encodeSigHeader(hasher, header, c) hasher.Sum(hash[:0]) + return hash } @@ -180,6 +188,7 @@ func encodeSigHeader(w io.Writer, header *types.Header, c *chain.BorConfig) { enc = append(enc, header.BaseFee) } } + if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) } @@ -193,9 +202,11 @@ func CalcProducerDelay(number uint64, succession int, c *chain.BorConfig) uint64 if number%c.CalculateSprint(number) == 0 { delay = c.CalculateProducerDelay(number) } + if succession > 0 { delay += uint64(succession) * c.CalculateBackupMultiplier(number) } + return delay } @@ -209,6 +220,7 @@ func CalcProducerDelay(number uint64, succession int, c *chain.BorConfig) uint64 func BorRLP(header *types.Header, c *chain.BorConfig) []byte { b := new(bytes.Buffer) encodeSigHeader(b, header, c) + return b.Bytes() } @@ -221,30 +233,34 @@ type Bor struct { recents *lru.ARCCache // Snapshots for recent block to speed up reorgs signatures *lru.ARCCache // Signatures of recent blocks to speed up mining - signer libcommon.Address // Ethereum address of the signing key - signFn SignerFn // Signer function to authorize hashes with - lock *sync.RWMutex // Protects the signer fields + authorizedSigner atomic.Pointer[signer] // Ethereum address and sign function of the signing key execCtx context.Context // context of caller execution stage - GenesisContractsClient *GenesisContractsClient - validatorSetABI abi.ABI - stateReceiverABI abi.ABI + spanner Spanner + GenesisContractsClient GenesisContract HeimdallClient IHeimdallClient - WithoutHeimdall bool // scope event.SubscriptionScope // The fields below are for testing only fakeDiff bool // Skip difficulty verifications spanCache *btree.BTree + + closeOnce sync.Once +} + +type signer struct { + signer libcommon.Address // Ethereum address of the signing key + signFn SignerFn // Signer function to authorize hashes with } // New creates a Matic Bor consensus engine. func New( chainConfig *chain.Config, db kv.RwDB, - heimdallURL string, - withoutHeimdall bool, + spanner Spanner, + heimdallClient IHeimdallClient, + genesisContracts GenesisContract, ) *Bor { // get bor config borConfig := chainConfig.Bor @@ -257,26 +273,27 @@ func New( // 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) c := &Bor{ chainConfig: chainConfig, config: borConfig, DB: db, recents: recents, signatures: signatures, - validatorSetABI: vABI, - stateReceiverABI: sABI, - GenesisContractsClient: genesisContractsClient, + spanner: spanner, + GenesisContractsClient: genesisContracts, HeimdallClient: heimdallClient, - WithoutHeimdall: withoutHeimdall, spanCache: btree.New(32), execCtx: context.Background(), - lock: &sync.RWMutex{}, } + c.authorizedSigner.Store(&signer{ + libcommon.Address{}, + func(_ libcommon.Address, _ string, i []byte) ([]byte, error) { + // return an error to prevent panics + return nil, &UnauthorizedSignerError{0, libcommon.Address{}.Bytes()} + }, + }) + // make sure we can decode all the GenesisAlloc in the BorConfig. for key, genesisAlloc := range c.config.BlockAlloc { if _, err := decodeGenesisAlloc(genesisAlloc); err != nil { @@ -306,10 +323,26 @@ func (c *Bor) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Head return c.verifyHeader(chain, header, nil) } -func (c *Bor) WithExecutionContext(ctx context.Context) *Bor { - subclient := *c - subclient.execCtx = ctx - return &subclient +// 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, _ []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 @@ -320,6 +353,7 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head if header.Number == nil { return errUnknownBlock } + number := header.Number.Uint64() // Don't waste time checking blocks from the future @@ -339,27 +373,40 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head 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 != (libcommon.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 } } + + // Verify that the gas limit is <= 2^63-1 + gasCap := uint64(0x7fffffffffffffff) + + if header.GasLimit > gasCap { + return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, gasCap) + } + // 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) } @@ -370,9 +417,11 @@ func validateHeaderExtraField(extraBytes []byte) error { if len(extraBytes) < extraVanity { return errMissingVanity } + if len(extraBytes) < extraVanity+extraSeal { return errMissingSignature } + return nil } @@ -383,12 +432,14 @@ func validateHeaderExtraField(extraBytes []byte) error { 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 { @@ -403,6 +454,7 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainHeaderReader, header *t if header.GasUsed > header.GasLimit { return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) } + if !chain.Config().IsLondon(header.Number.Uint64()) { // Verify BaseFee not present before EIP-1559 fork. if header.BaseFee != nil { @@ -437,7 +489,7 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainHeaderReader, header *t currentValidators := snap.ValidatorSet.Copy().Validators // sort validator by address - sort.Sort(ValidatorsByAddress(currentValidators)) + sort.Sort(valset.ValidatorsByAddress(currentValidators)) for i, validator := range currentValidators { copy(validatorsBytes[i*validatorHeaderBytesLength:], validator.HeaderBytes()) } @@ -454,144 +506,121 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainHeaderReader, header *t // snapshot retrieves the authorization snapshot at a given point in time. func (c *Bor) snapshot(chain consensus.ChainHeaderReader, number uint64, hash libcommon.Hash, parents []*types.Header) (*Snapshot, error) { // Search for a snapshot in memory or on disk for checkpoints - var ( - snap *Snapshot - ) - - cont := true // Continue applying snapshots - limit := 256 - for cont { - var headersList [][]*types.Header // List of lists because we will apply headers to snapshot such that we can persist snapshot after every list - var headers []*types.Header - h := hash - n := number - p := parents - cont = false - for snap == nil { - // If an in-memory snapshot was found, use that - if s, ok := c.recents.Get(h); ok { - snap = s.(*Snapshot) + var snap *Snapshot + + headers := make([]*types.Header, 0, 16) + + //nolint:govet + 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); err == nil { + log.Trace("Loaded snapshot from disk", "number", number, "hash", hash) + + snap = s + break } + } - // If an on-disk checkpoint snapshot can be found, use that - if n%checkpointInterval == 0 { - if s, err := loadSnapshot(c.config, c.signatures, c.DB, h); err == nil { - log.Trace("Loaded snapshot from disk", "number", n, "hash", h) - 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 + // nolint:nestif + if number == 0 { + checkpoint := chain.GetHeaderByNumber(number) + if checkpoint != nil { + // get checkpoint data + hash := checkpoint.Hash() + + // get validators and current span + validators, err := c.spanner.GetCurrentValidators(number+1, c.authorizedSigner.Load().signer, c.getSpanForBlock) + if err != nil { + return nil, err } - } - // 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 n == 0 { - checkpoint := chain.GetHeaderByNumber(n) - if checkpoint != nil { - // get checkpoint data - h := checkpoint.Hash() - - // get validators and current span - validators, err := c.GetCurrentValidators(n + 1) - if err != nil { - return nil, err - } - - // new snap shot - snap = newSnapshot(c.config, c.signatures, n, h, validators) - if err := snap.store(c.DB); err != nil { - return nil, err - } - log.Info("Stored checkpoint snapshot to disk", "number", n, "hash", h) - break + // new snap shot + snap = newSnapshot(c.config, c.signatures, number, hash, validators) + if err := snap.store(c.DB); err != nil { + return nil, err } - } - // No snapshot for this header, gather the header and move backward - var header *types.Header - if len(p) > 0 { - // If we have explicit parents, pick from there (enforced) - header = p[len(p)-1] - if header.Hash() != h || header.Number.Uint64() != n { - return nil, consensus.ErrUnknownAncestor - } - p = p[:len(p)-1] - } else { - // No explicit parents (or no more left), reach out to the database - header = chain.GetHeader(h, n) - if header == nil { - return nil, consensus.ErrUnknownAncestor - } - } - if n%checkpointInterval == 0 && len(headers) > 0 { - headersList = append(headersList, headers) - if len(headersList) > limit { - headersList = headersList[1:] - cont = true - } - headers = nil - } - headers = append(headers, header) - n-- - h = header.ParentHash - } + log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash) - // check if snapshot is nil - if snap == nil { - return nil, fmt.Errorf("unknown error while retrieving snapshot at block number %v", n) - } - if len(headers) > 0 { - headersList = append(headersList, headers) + break + } } - // Previous snapshot found, apply any pending headers on top of it - if cont { - lastList := headersList[len(headersList)-1] - firstList := headersList[0] - log.Info("Applying headers to snapshot", "from", lastList[len(lastList)-1].Number.Uint64(), "to", firstList[0].Number.Uint64()) - } - for i := 0; i < len(headersList)/2; i++ { - headersList[i], headersList[len(headersList)-1-i] = headersList[len(headersList)-1-i], headersList[i] - } - for j := 0; j < len(headersList); j++ { - hs := headersList[j] - for i := 0; i < len(hs)/2; i++ { - hs[i], hs[len(hs)-1-i] = hs[len(hs)-1-i], hs[i] + // 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 } - var err error - snap, err = snap.apply(hs) - if err != nil { - return nil, err - } - c.recents.Add(snap.Hash, snap) - if snap.Number%checkpointInterval == 0 { - // We've generated a new checkpoint snapshot, save to disk - if err = snap.store(c.DB); err != nil { - return nil, err - } - log.Trace("Stored snapshot to disk", "number", snap.Number, "hash", snap.Hash) + 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 } } - if cont { - snap = nil + + 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, nil + return snap, err } // VerifyUncles implements consensus.Engine, always returning an error for any // uncles as this consensus mechanism doesn't permit uncles. // 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, header *types.Header, uncles []*types.Header) error { +func (c *Bor) VerifyUncles(_ consensus.ChainReader, _ *types.Header, uncles []*types.Header) error { if len(uncles) > 0 { - return errors.New("uncles not allowed") + return errUncleDetected } + return nil } @@ -611,18 +640,19 @@ func (c *Bor) verifySeal(chain consensus.ChainHeaderReader, header *types.Header if number == 0 { return errUnknownBlock } - // Resolve the authorization key and check against signers signer, err := ecrecover(header, c.signatures, c.config) if err != nil { return err } + // 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 } - if !snap.ValidatorSet.HasAddress(signer.Bytes()) { + + if !snap.ValidatorSet.HasAddress(signer) { // Check the UnauthorizedSignerError.Error() msg to see why we pass number-1 return &UnauthorizedSignerError{number - 1, signer.Bytes()} } @@ -654,10 +684,13 @@ func (c *Bor) verifySeal(chain consensus.ChainHeaderReader, header *types.Header return nil } +func IsBlockOnTime(parent *types.Header, header *types.Header, number uint64, succession int, cfg *chain.BorConfig) bool { + return parent != nil && header.Time < parent.Time+CalcProducerDelay(number, succession, cfg) +} + // 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, state *state.IntraBlockState) error { - // If the block isn't a checkpoint, cast a random vote (good enough for now) header.Coinbase = libcommon.Address{} header.Nonce = types.BlockNonce{} @@ -670,23 +703,25 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s } // Set the correct difficulty - header.Difficulty = new(big.Int).SetUint64(snap.Difficulty(c.signer)) + header.Difficulty = new(big.Int).SetUint64(snap.Difficulty(c.authorizedSigner.Load().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 isSprintStart(number+1, c.config.CalculateSprint(number)) { - newValidators, err := c.GetCurrentValidators(number + 1) + newValidators, err := c.spanner.GetCurrentValidators(number+1, c.authorizedSigner.Load().signer, c.getSpanForBlock) if err != nil { - return errors.New("unknown validators") + return errUnknownValidators } // sort validator by address - sort.Sort(ValidatorsByAddress(newValidators)) + sort.Sort(valset.ValidatorsByAddress(newValidators)) + for _, validator := range newValidators { header.Extra = append(header.Extra, validator.HeaderBytes()...) } @@ -706,8 +741,8 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s var succession int // if signer is not empty - if !bytes.Equal(c.signer.Bytes(), libcommon.Address{}.Bytes()) { - succession, err = snap.GetSignerSuccessionNumber(c.signer) + if !bytes.Equal(c.authorizedSigner.Load().signer.Bytes(), libcommon.Address{}.Bytes()) { + succession, err = snap.GetSignerSuccessionNumber(c.authorizedSigner.Load().signer) if err != nil { return err } @@ -717,6 +752,7 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s if header.Time < uint64(time.Now().Unix()) { header.Time = uint64(time.Now().Unix()) } + return nil } @@ -727,16 +763,18 @@ func (c *Bor) Finalize(config *chain.Config, header *types.Header, state *state. e consensus.EpochReader, chain consensus.ChainHeaderReader, syscall consensus.SystemCall, ) (types.Transactions, types.Receipts, error) { var err error + headerNumber := header.Number.Uint64() + if isSprintStart(headerNumber, c.config.CalculateSprint(headerNumber)) { - cx := chainContext{Chain: chain, Bor: c} + cx := statefull.ChainContext{Chain: chain, Bor: c} // check and commit span if err := c.checkAndCommitSpan(state, header, cx, syscall); err != nil { log.Error("Error while committing span", "err", err) return nil, types.Receipts{}, err } - if !c.WithoutHeimdall { + if c.HeimdallClient != nil { // commit states _, err = c.CommitStates(state, header, cx, syscall) if err != nil { @@ -763,13 +801,16 @@ func (c *Bor) Finalize(config *chain.Config, header *types.Header, state *state. func decodeGenesisAlloc(i interface{}) (core.GenesisAlloc, error) { var alloc core.GenesisAlloc + b, err := json.Marshal(i) if err != nil { return nil, err } + if err := json.Unmarshal(b, &alloc); err != nil { return nil, err } + return alloc, nil } @@ -780,12 +821,14 @@ func (c *Bor) changeContractCodeIfNeeded(headerNumber uint64, state *state.Intra if err != nil { return fmt.Errorf("failed to decode genesis alloc: %v", err) } + for addr, account := range allocs { log.Trace("change contract code", "address", addr) state.SetCode(addr, account.Code) } } } + return nil } @@ -799,7 +842,7 @@ func (c *Bor) FinalizeAndAssemble(chainConfig *chain.Config, header *types.Heade headerNumber := header.Number.Uint64() if isSprintStart(headerNumber, c.config.CalculateSprint(headerNumber)) { - cx := chainContext{Chain: chain, Bor: c} + cx := statefull.ChainContext{Chain: chain, Bor: c} // check and commit span err := c.checkAndCommitSpan(state, header, cx, syscall) @@ -808,7 +851,7 @@ func (c *Bor) FinalizeAndAssemble(chainConfig *chain.Config, header *types.Heade return nil, nil, types.Receipts{}, err } - if !c.WithoutHeimdall { + if c.HeimdallClient != nil { // commit states _, err = c.CommitStates(state, header, cx, syscall) if err != nil { @@ -848,12 +891,11 @@ func (c *Bor) Initialize(config *chain.Config, chain consensus.ChainHeaderReader // Authorize injects a private key into the consensus engine to mint new blocks // with. -func (c *Bor) Authorize(signer libcommon.Address, signFn SignerFn) { - c.lock.Lock() - defer c.lock.Unlock() - - c.signer = signer - c.signFn = signFn +func (c *Bor) Authorize(currentSigner libcommon.Address, signFn SignerFn) { + c.authorizedSigner.Store(&signer{ + signer: currentSigner, + signFn: signFn, + }) } // Seal implements consensus.Engine, attempting to create a sealed block using @@ -862,18 +904,20 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result 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.CalculatePeriod(number) == 0 && len(block.Transactions()) == 0 { log.Trace("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() + currentSigner := c.authorizedSigner.Load() + signer, signFn := currentSigner.signer, currentSigner.signFn snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) if err != nil { @@ -881,7 +925,7 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result } // Bail out if we're unauthorized to sign a block - if !snap.ValidatorSet.HasAddress(signer.Bytes()) { + if !snap.ValidatorSet.HasAddress(signer) { // Check the UnauthorizedSignerError.Error() msg to see why we pass number-1 return &UnauthorizedSignerError{number - 1, signer.Bytes()} } @@ -904,7 +948,8 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result copy(header.Extra[len(header.Extra)-extraSeal:], sighash) // Wait until sealing is terminated or delay timeout. - log.Info("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) + log.Info("Waiting for slot to sign and propagate", "number", number, "hash", header.Hash, "delay-in-sec", uint(delay), "delay", common.PrettyDuration(delay)) + go func() { select { case <-stop: @@ -919,6 +964,7 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result "in-turn-signer", snap.ValidatorSet.GetProposer().Address.Hex(), ) } + log.Info( "Sealing successful", "number", number, @@ -951,7 +997,8 @@ func (c *Bor) CalcDifficulty(chain consensus.ChainHeaderReader, _, _ uint64, _ * if err != nil { return nil } - return new(big.Int).SetUint64(snap.Difficulty(c.signer)) + + return new(big.Int).SetUint64(snap.Difficulty(c.authorizedSigner.Load().signer)) } // SealHash returns the hash of a block prior to it being sealed. @@ -976,180 +1023,124 @@ func (c *Bor) APIs(chain consensus.ChainHeaderReader) []rpc.API { // Close implements consensus.Engine. It's a noop for bor as there are no background threads. func (c *Bor) Close() error { - c.DB.Close() - return nil -} - -// GetCurrentSpan get current span from contract -func (c *Bor) GetCurrentSpan(header *types.Header, state *state.IntraBlockState, chain chainContext, syscall consensus.SystemCall) (*Span, error) { - - // method - method := "getCurrentSpan" - data, err := c.validatorSetABI.Pack(method) - if err != nil { - log.Error("Unable to pack tx for getCurrentSpan", "err", err) - return nil, err - } - - result, err := syscall(libcommon.HexToAddress(c.config.ValidatorContract), data) - 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(blockNumber uint64) ([]*Validator, error) { - // Use signer as validator in case of bor devent - if c.chainConfig.ChainName == networkname.BorDevnetChainName { - validators := []*Validator{ - { - ID: 1, - Address: c.signer, - VotingPower: 1000, - ProposerPriority: 1, - }, + c.closeOnce.Do(func() { + if c.HeimdallClient != nil { + c.HeimdallClient.Close() } + }) - return validators, nil - } - - span, err := c.getSpanForBlock(blockNumber) - if err != nil { - return nil, err - } - return span.ValidatorSet.Validators, nil + return nil } func (c *Bor) checkAndCommitSpan( state *state.IntraBlockState, header *types.Header, - chain chainContext, + chain statefull.ChainContext, syscall consensus.SystemCall, ) error { headerNumber := header.Number.Uint64() - span, err := c.GetCurrentSpan(header, state, chain, syscall) + + span, err := c.spanner.GetCurrentSpan(syscall) if err != nil { return err } + if c.needToCommitSpan(span, headerNumber) { err := c.fetchAndCommitSpan(span.ID+1, state, header, chain, syscall) return err } + return nil } -func (c *Bor) needToCommitSpan(span *Span, headerNumber uint64) bool { +func (c *Bor) needToCommitSpan(currentSpan *span.Span, headerNumber uint64) bool { // if span is nil - if span == nil { + if currentSpan == nil { return false } // check span is not set initially - if span.EndBlock == 0 { + if currentSpan.EndBlock == 0 { return true } // if current block is first block of last sprint in current span - if span.EndBlock > c.config.CalculateSprint(headerNumber) && span.EndBlock-c.config.CalculateSprint(headerNumber)+1 == headerNumber { + if currentSpan.EndBlock > c.config.CalculateSprint(headerNumber) && currentSpan.EndBlock-c.config.CalculateSprint(headerNumber)+1 == headerNumber { return true } return false } -func (c *Bor) getSpanForBlock(blockNum uint64) (*HeimdallSpan, error) { +func (c *Bor) getSpanForBlock(blockNum uint64) (*span.HeimdallSpan, error) { log.Info("Getting span", "for block", blockNum) - var span *HeimdallSpan - c.spanCache.AscendGreaterOrEqual(&HeimdallSpan{Span: Span{EndBlock: blockNum}}, func(item btree.Item) bool { - span = item.(*HeimdallSpan) + var borSpan *span.HeimdallSpan + c.spanCache.AscendGreaterOrEqual(&span.HeimdallSpan{Span: span.Span{EndBlock: blockNum}}, func(item btree.Item) bool { + borSpan = item.(*span.HeimdallSpan) return false }) - if span == nil { + + if borSpan == nil { // Span with high enough block number is not loaded var spanID uint64 if c.spanCache.Len() > 0 { - spanID = c.spanCache.Max().(*HeimdallSpan).ID + 1 + spanID = c.spanCache.Max().(*span.HeimdallSpan).ID + 1 } - for span == nil || span.EndBlock < blockNum { - var heimdallSpan HeimdallSpan + for borSpan == nil || borSpan.EndBlock < blockNum { log.Info("Span with high enough block number is not loaded", "fetching span", spanID) - response, err := c.HeimdallClient.FetchWithRetry(c.execCtx, fmt.Sprintf("bor/span/%d", spanID), "") + response, err := c.HeimdallClient.Span(c.execCtx, spanID) if err != nil { return nil, err } - if err := json.Unmarshal(response.Result, &heimdallSpan); err != nil { - return nil, err - } - span = &heimdallSpan - c.spanCache.ReplaceOrInsert(span) + borSpan = response + c.spanCache.ReplaceOrInsert(borSpan) spanID++ } } else { - for span.StartBlock > blockNum { + for borSpan.StartBlock > blockNum { // Span wit low enough block number is not loaded - var spanID = span.ID - 1 - var heimdallSpan HeimdallSpan + var spanID = borSpan.ID - 1 log.Info("Span with low enough block number is not loaded", "fetching span", spanID) - response, err := c.HeimdallClient.FetchWithRetry(c.execCtx, fmt.Sprintf("bor/span/%d", spanID), "") + response, err := c.HeimdallClient.Span(c.execCtx, spanID) if err != nil { return nil, err } - if err := json.Unmarshal(response.Result, &heimdallSpan); err != nil { - return nil, err - } - span = &heimdallSpan - c.spanCache.ReplaceOrInsert(span) + borSpan = response + c.spanCache.ReplaceOrInsert(borSpan) } } + for c.spanCache.Len() > 128 { c.spanCache.DeleteMin() } - return span, nil + + return borSpan, nil } func (c *Bor) fetchAndCommitSpan( newSpanID uint64, state *state.IntraBlockState, header *types.Header, - chain chainContext, + chain statefull.ChainContext, syscall consensus.SystemCall, ) error { - var heimdallSpan HeimdallSpan + var heimdallSpan span.HeimdallSpan - if c.WithoutHeimdall { + if c.HeimdallClient == nil { + // fixme: move to a new mock or fake and remove c.HeimdallClient completely s, err := c.getNextHeimdallSpanForTest(newSpanID, state, header, chain, syscall) if err != nil { return err } + heimdallSpan = *s } else { - response, err := c.HeimdallClient.FetchWithRetry(c.execCtx, fmt.Sprintf("bor/span/%d", newSpanID), "") + response, err := c.HeimdallClient.Span(c.execCtx, newSpanID) if err != nil { return err } - if err := json.Unmarshal(response.Result, &heimdallSpan); err != nil { - return err - } + heimdallSpan = *response } // check if chain id matches with heimdall span @@ -1161,79 +1152,37 @@ func (c *Bor) fetchAndCommitSpan( ) } - // get validators bytes - validators := make([]MinimalVal, 0, len(heimdallSpan.ValidatorSet.Validators)) - for _, val := range heimdallSpan.ValidatorSet.Validators { - validators = append(validators, val.MinimalVal()) - } - validatorBytes, err := rlp.EncodeToBytes(validators) - if err != nil { - return err - } - - // get producers bytes - producers := make([]MinimalVal, 0, len(heimdallSpan.SelectedProducers)) - for _, val := range heimdallSpan.SelectedProducers { - producers = append(producers, val.MinimalVal()) - } - producerBytes, err := rlp.EncodeToBytes(producers) - if err != nil { - return err - } - - // method - method := "commitSpan" - log.Debug("✅ 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", "err", err) - return err - } - - _, err = syscall(libcommon.HexToAddress(c.config.ValidatorContract), data) - // apply message - return err + return c.spanner.CommitSpan(heimdallSpan, syscall) } // CommitStates commit states func (c *Bor) CommitStates( state *state.IntraBlockState, header *types.Header, - chain chainContext, + chain statefull.ChainContext, syscall consensus.SystemCall, ) ([]*types.StateSyncData, error) { stateSyncs := make([]*types.StateSyncData, 0) number := header.Number.Uint64() - _lastStateID, err := c.GenesisContractsClient.LastStateId(header, state, chain, c, syscall) + + _lastStateID, err := c.GenesisContractsClient.LastStateId(syscall) if err != nil { return nil, err } to := time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.CalculateSprint(number)).Time), 0) lastStateID := _lastStateID.Uint64() - log.Trace( + + log.Info( "Fetching state updates from Heimdall", "fromID", lastStateID+1, "to", to.Format(time.RFC3339)) - eventRecords, err := c.HeimdallClient.FetchStateSyncEvents(c.execCtx, lastStateID+1, to.Unix()) + eventRecords, err := c.HeimdallClient.StateSyncEvents(c.execCtx, lastStateID+1, to.Unix()) if err != nil { return nil, err } + if c.config.OverrideStateSyncRecords != nil { if val, ok := c.config.OverrideStateSyncRecords[strconv.FormatUint(number, 10)]; ok { eventRecords = eventRecords[0:val] @@ -1245,6 +1194,7 @@ func (c *Bor) CommitStates( if eventRecord.ID <= lastStateID { continue } + if err := validateEventRecord(eventRecord, number, to, lastStateID, chainID); err != nil { log.Error(err.Error()) break @@ -1258,19 +1208,21 @@ func (c *Bor) CommitStates( } stateSyncs = append(stateSyncs, &stateData) - if err := c.GenesisContractsClient.CommitState(eventRecord, state, header, chain, c, syscall); err != nil { + if err := c.GenesisContractsClient.CommitState(eventRecord, syscall); err != nil { return nil, err } lastStateID++ } + return stateSyncs, nil } -func validateEventRecord(eventRecord *EventRecordWithTime, number uint64, to time.Time, lastStateID uint64, chainID string) error { +func validateEventRecord(eventRecord *clerk.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 } @@ -1278,6 +1230,10 @@ func (c *Bor) SetHeimdallClient(h IHeimdallClient) { c.HeimdallClient = h } +func (c *Bor) GetCurrentValidators(blockNumber uint64, signer libcommon.Address, getSpanForBlock func(blockNum uint64) (*span.HeimdallSpan, error)) ([]*valset.Validator, error) { + return c.spanner.GetCurrentValidators(blockNumber, signer, getSpanForBlock) +} + // // Private methods // @@ -1286,11 +1242,12 @@ func (c *Bor) getNextHeimdallSpanForTest( newSpanID uint64, state *state.IntraBlockState, header *types.Header, - chain chainContext, + chain statefull.ChainContext, syscall consensus.SystemCall, -) (*HeimdallSpan, error) { +) (*span.HeimdallSpan, error) { headerNumber := header.Number.Uint64() - span, err := c.GetCurrentSpan(header, state, chain, syscall) + + spanBor, err := c.spanner.GetCurrentSpan(syscall) if err != nil { return nil, err } @@ -1302,20 +1259,22 @@ func (c *Bor) getNextHeimdallSpanForTest( } // new span - span.ID = newSpanID - if span.EndBlock == 0 { - span.StartBlock = 256 + spanBor.ID = newSpanID + if spanBor.EndBlock == 0 { + spanBor.StartBlock = 256 } else { - span.StartBlock = span.EndBlock + 1 + spanBor.StartBlock = spanBor.EndBlock + 1 } - span.EndBlock = span.StartBlock + (100 * c.config.CalculateSprint(headerNumber)) - 1 - selectedProducers := make([]Validator, len(snap.ValidatorSet.Validators)) + spanBor.EndBlock = spanBor.StartBlock + (100 * c.config.CalculateSprint(headerNumber)) - 1 + + selectedProducers := make([]valset.Validator, len(snap.ValidatorSet.Validators)) for i, v := range snap.ValidatorSet.Validators { selectedProducers[i] = *v } - heimdallSpan := &HeimdallSpan{ - Span: *span, + + heimdallSpan := &span.HeimdallSpan{ + Span: *spanBor, ValidatorSet: *snap.ValidatorSet, SelectedProducers: selectedProducers, ChainID: c.chainConfig.ChainID.String(), @@ -1324,38 +1283,22 @@ func (c *Bor) getNextHeimdallSpanForTest( 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 libcommon.Hash, number uint64) *types.Header { - return c.Chain.GetHeader(hash, number) -} - -func validatorContains(a []*Validator, x *Validator) (*Validator, bool) { +func validatorContains(a []*valset.Validator, x *valset.Validator) (*valset.Validator, bool) { for _, n := range a { if bytes.Equal(n.Address.Bytes(), x.Address.Bytes()) { return n, true } } + return nil, false } -func getUpdatedValidatorSet(oldValidatorSet *ValidatorSet, newVals []*Validator) *ValidatorSet { +func getUpdatedValidatorSet(oldValidatorSet *valset.ValidatorSet, newVals []*valset.Validator) *valset.ValidatorSet { v := oldValidatorSet oldVals := v.Validators - changes := make([]*Validator, 0, len(oldVals)) + changes := make([]*valset.Validator, 0, len(oldVals)) + for _, ov := range oldVals { if f, ok := validatorContains(newVals, ov); ok { ov.VotingPower = f.VotingPower @@ -1372,7 +1315,10 @@ func getUpdatedValidatorSet(oldValidatorSet *ValidatorSet, newVals []*Validator) } } - v.UpdateWithChangeSet(changes) + if err := v.UpdateWithChangeSet(changes); err != nil { + log.Error("Error while updating change set", "error", err) + } + return v } diff --git a/consensus/bor/clerk.go b/consensus/bor/clerk/clerk.go similarity index 98% rename from consensus/bor/clerk.go rename to consensus/bor/clerk/clerk.go index 8074ec62718..1069ce8d9a4 100644 --- a/consensus/bor/clerk.go +++ b/consensus/bor/clerk/clerk.go @@ -1,4 +1,4 @@ -package bor +package clerk import ( "fmt" diff --git a/consensus/bor/contract/client.go b/consensus/bor/contract/client.go new file mode 100644 index 00000000000..1a7508c2188 --- /dev/null +++ b/consensus/bor/contract/client.go @@ -0,0 +1,106 @@ +package contract + +import ( + "math/big" + "strings" + + "github.com/ledgerwatch/erigon-lib/chain" + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/accounts/abi" + "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/consensus/bor/clerk" + "github.com/ledgerwatch/erigon/rlp" + "github.com/ledgerwatch/log/v3" +) + +var ( + vABI, _ = abi.JSON(strings.NewReader(ValidatorsetABI)) + sABI, _ = abi.JSON(strings.NewReader(StateReceiverABI)) +) + +func ValidatorSet() abi.ABI { + return vABI +} + +func StateReceiver() abi.ABI { + return sABI +} + +type GenesisContractsClient struct { + validatorSetABI abi.ABI + stateReceiverABI abi.ABI + ValidatorContract string + StateReceiverContract string + chainConfig *chain.Config +} + +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"}]` + 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 *chain.Config, + validatorContract, + stateReceiverContract string, +) *GenesisContractsClient { + return &GenesisContractsClient{ + validatorSetABI: ValidatorSet(), + stateReceiverABI: StateReceiver(), + ValidatorContract: validatorContract, + StateReceiverContract: stateReceiverContract, + chainConfig: chainConfig, + } +} + +func (gc *GenesisContractsClient) CommitState( + event *clerk.EventRecordWithTime, + syscall consensus.SystemCall, +) error { + eventRecord := event.BuildEventRecord() + + recordBytes, err := rlp.EncodeToBytes(eventRecord) + if err != nil { + return err + } + + const 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", "err", err) + return err + } + + log.Trace("→ committing new state", "eventRecord", event.String()) + _, err = syscall(libcommon.HexToAddress(gc.StateReceiverContract), data) + + if err != nil { + return err + } + return nil +} + +func (gc *GenesisContractsClient) LastStateId(syscall consensus.SystemCall, +) (*big.Int, error) { + const method = "lastStateId" + + data, err := gc.stateReceiverABI.Pack(method) + if err != nil { + log.Error("Unable to pack tx for LastStateId", "err", err) + return nil, err + } + + result, err := syscall(libcommon.HexToAddress(gc.StateReceiverContract), data) + 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/errors.go b/consensus/bor/errors.go index a1e60d1e219..c70aff344a0 100644 --- a/consensus/bor/errors.go +++ b/consensus/bor/errors.go @@ -3,36 +3,9 @@ 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, - ) -} + "github.com/ledgerwatch/erigon/consensus/bor/clerk" +) type MaxCheckpointLengthExceededError struct { Start uint64 @@ -129,12 +102,12 @@ type InvalidStateReceivedError struct { Number uint64 LastStateID uint64 To *time.Time - Event *EventRecordWithTime + Event *clerk.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", + "Received invalid event %v at block %d. Requested events until %s. Last state id was %d", e.Event, e.Number, e.To.Format(time.RFC3339), diff --git a/consensus/bor/genesis.go b/consensus/bor/genesis.go new file mode 100644 index 00000000000..5096b33c57f --- /dev/null +++ b/consensus/bor/genesis.go @@ -0,0 +1,14 @@ +package bor + +import ( + "math/big" + + "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/consensus/bor/clerk" +) + +//go:generate mockgen -destination=./genesis_contract_mock.go -package=bor . GenesisContract +type GenesisContract interface { + CommitState(event *clerk.EventRecordWithTime, syscall consensus.SystemCall) error + LastStateId(syscall consensus.SystemCall) (*big.Int, error) +} diff --git a/consensus/bor/genesis_contract_mock.go b/consensus/bor/genesis_contract_mock.go new file mode 100644 index 00000000000..6cba2c64b18 --- /dev/null +++ b/consensus/bor/genesis_contract_mock.go @@ -0,0 +1,66 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ledgerwatch/erigon/consensus/bor (interfaces: GenesisContract) + +// Package bor is a generated GoMock package. +package bor + +import ( + big "math/big" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + consensus "github.com/ledgerwatch/erigon/consensus" + clerk "github.com/ledgerwatch/erigon/consensus/bor/clerk" +) + +// MockGenesisContract is a mock of GenesisContract interface. +type MockGenesisContract struct { + ctrl *gomock.Controller + recorder *MockGenesisContractMockRecorder +} + +// MockGenesisContractMockRecorder is the mock recorder for MockGenesisContract. +type MockGenesisContractMockRecorder struct { + mock *MockGenesisContract +} + +// NewMockGenesisContract creates a new mock instance. +func NewMockGenesisContract(ctrl *gomock.Controller) *MockGenesisContract { + mock := &MockGenesisContract{ctrl: ctrl} + mock.recorder = &MockGenesisContractMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGenesisContract) EXPECT() *MockGenesisContractMockRecorder { + return m.recorder +} + +// CommitState mocks base method. +func (m *MockGenesisContract) CommitState(arg0 *clerk.EventRecordWithTime, arg1 consensus.SystemCall) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitState", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitState indicates an expected call of CommitState. +func (mr *MockGenesisContractMockRecorder) CommitState(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitState", reflect.TypeOf((*MockGenesisContract)(nil).CommitState), arg0, arg1) +} + +// LastStateId mocks base method. +func (m *MockGenesisContract) LastStateId(arg0 consensus.SystemCall) (*big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LastStateId", arg0) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LastStateId indicates an expected call of LastStateId. +func (mr *MockGenesisContractMockRecorder) LastStateId(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastStateId", reflect.TypeOf((*MockGenesisContract)(nil).LastStateId), arg0) +} diff --git a/consensus/bor/genesis_contracts_client.go b/consensus/bor/genesis_contracts_client.go deleted file mode 100644 index e64c83ccd39..00000000000 --- a/consensus/bor/genesis_contracts_client.go +++ /dev/null @@ -1,96 +0,0 @@ -package bor - -import ( - "math/big" - "strings" - - "github.com/ledgerwatch/erigon-lib/chain" - libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/log/v3" - - "github.com/ledgerwatch/erigon/accounts/abi" - "github.com/ledgerwatch/erigon/consensus" - "github.com/ledgerwatch/erigon/core/state" - "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/rlp" -) - -type GenesisContractsClient struct { - validatorSetABI abi.ABI - stateReceiverABI abi.ABI - ValidatorContract string - StateReceiverContract string - chainConfig *chain.Config -} - -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 *chain.Config, - validatorContract, - stateReceiverContract string, -) *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, - } -} - -func (gc *GenesisContractsClient) CommitState( - event *EventRecordWithTime, - state *state.IntraBlockState, - header *types.Header, - chCtx chainContext, - c *Bor, - syscall consensus.SystemCall, -) 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", "err", err) - return err - } - log.Trace("→ committing new state", "eventRecord", event.String()) - _, err = syscall(libcommon.HexToAddress(gc.StateReceiverContract), data) - if err != nil { - return err - } - return nil -} - -func (gc *GenesisContractsClient) LastStateId(header *types.Header, - state *state.IntraBlockState, - chain chainContext, - c *Bor, - syscall consensus.SystemCall, -) (*big.Int, error) { - method := "lastStateId" - data, err := gc.stateReceiverABI.Pack(method) - if err != nil { - log.Error("Unable to pack tx for LastStateId", "err", err) - return nil, err - } - - result, err := syscall(libcommon.HexToAddress(gc.StateReceiverContract), data) - 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/heimall.go b/consensus/bor/heimall.go new file mode 100644 index 00000000000..17a01819c3a --- /dev/null +++ b/consensus/bor/heimall.go @@ -0,0 +1,18 @@ +package bor + +import ( + "context" + + "github.com/ledgerwatch/erigon/consensus/bor/clerk" + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/checkpoint" + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" +) + +//go:generate mockgen -destination=../../tests/bor/mocks/IHeimdallClient.go -package=mocks . IHeimdallClient +type IHeimdallClient interface { + StateSyncEvents(ctx context.Context, fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) + Span(ctx context.Context, spanID uint64) (*span.HeimdallSpan, error) + FetchCheckpoint(ctx context.Context, number int64) (*checkpoint.Checkpoint, error) + FetchCheckpointCount(ctx context.Context) (int64, error) + Close() +} diff --git a/consensus/bor/heimdall/checkpoint/checkpoint.go b/consensus/bor/heimdall/checkpoint/checkpoint.go new file mode 100644 index 00000000000..258cadd7e76 --- /dev/null +++ b/consensus/bor/heimdall/checkpoint/checkpoint.go @@ -0,0 +1,31 @@ +package checkpoint + +import ( + "math/big" + + libcommon "github.com/ledgerwatch/erigon-lib/common" +) + +// Checkpoint defines a response object type of bor checkpoint +type Checkpoint struct { + Proposer libcommon.Address `json:"proposer"` + StartBlock *big.Int `json:"start_block"` + EndBlock *big.Int `json:"end_block"` + RootHash libcommon.Hash `json:"root_hash"` + BorChainID string `json:"bor_chain_id"` + Timestamp uint64 `json:"timestamp"` +} + +type CheckpointResponse struct { + Height string `json:"height"` + Result Checkpoint `json:"result"` +} + +type CheckpointCount struct { + Result int64 `json:"result"` +} + +type CheckpointCountResponse struct { + Height string `json:"height"` + Result CheckpointCount `json:"result"` +} diff --git a/consensus/bor/heimdall/client.go b/consensus/bor/heimdall/client.go new file mode 100644 index 00000000000..bfd20f22be5 --- /dev/null +++ b/consensus/bor/heimdall/client.go @@ -0,0 +1,330 @@ +package heimdall + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "time" + + "github.com/ledgerwatch/erigon/consensus/bor/clerk" + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/checkpoint" + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" + "github.com/ledgerwatch/log/v3" +) + +var ( + // // ErrShutdownDetected is returned if a shutdown was detected + ErrShutdownDetected = errors.New("shutdown detected") + ErrNoResponse = errors.New("got a nil response") + ErrNotSuccessfulResponse = errors.New("error while fetching data from Heimdall") +) + +const ( + stateFetchLimit = 50 + apiHeimdallTimeout = 5 * time.Second + retryCall = 5 * time.Second +) + +type StateSyncEventsResponse struct { + Height string `json:"height"` + Result []*clerk.EventRecordWithTime `json:"result"` +} + +type SpanResponse struct { + Height string `json:"height"` + Result span.HeimdallSpan `json:"result"` +} + +type HeimdallClient struct { + urlString string + client http.Client + closeCh chan struct{} +} + +type Request struct { + client http.Client + url *url.URL + start time.Time +} + +func NewHeimdallClient(urlString string) *HeimdallClient { + return &HeimdallClient{ + urlString: urlString, + client: http.Client{ + Timeout: apiHeimdallTimeout, + }, + closeCh: make(chan struct{}), + } +} + +const ( + fetchStateSyncEventsFormat = "from-id=%d&to-time=%d&limit=%d" + fetchStateSyncEventsPath = "clerk/event-record/list" + fetchCheckpoint = "/checkpoints/%s" + fetchCheckpointCount = "/checkpoints/count" + + fetchSpanFormat = "bor/span/%d" +) + +func (h *HeimdallClient) StateSyncEvents(ctx context.Context, fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) { + eventRecords := make([]*clerk.EventRecordWithTime, 0) + + for { + url, err := stateSyncURL(h.urlString, fromID, to) + if err != nil { + return nil, err + } + + log.Info("Fetching state sync events", "queryParams", url.RawQuery) + + ctx = withRequestType(ctx, stateSyncRequest) + + response, err := FetchWithRetry[StateSyncEventsResponse](ctx, h.client, url, h.closeCh) + if err != nil { + return nil, err + } + + if response == nil || response.Result == nil { + // status 204 + break + } + + eventRecords = append(eventRecords, response.Result...) + + if len(response.Result) < stateFetchLimit { + break + } + + fromID += uint64(stateFetchLimit) + } + + sort.SliceStable(eventRecords, func(i, j int) bool { + return eventRecords[i].ID < eventRecords[j].ID + }) + + return eventRecords, nil +} + +func (h *HeimdallClient) Span(ctx context.Context, spanID uint64) (*span.HeimdallSpan, error) { + url, err := spanURL(h.urlString, spanID) + if err != nil { + return nil, err + } + + ctx = withRequestType(ctx, spanRequest) + + response, err := FetchWithRetry[SpanResponse](ctx, h.client, url, h.closeCh) + if err != nil { + return nil, err + } + + return &response.Result, nil +} + +// FetchCheckpoint fetches the checkpoint from heimdall +func (h *HeimdallClient) FetchCheckpoint(ctx context.Context, number int64) (*checkpoint.Checkpoint, error) { + url, err := checkpointURL(h.urlString, number) + if err != nil { + return nil, err + } + + ctx = withRequestType(ctx, checkpointRequest) + + response, err := FetchWithRetry[checkpoint.CheckpointResponse](ctx, h.client, url, h.closeCh) + if err != nil { + return nil, err + } + + return &response.Result, nil +} + +// FetchCheckpointCount fetches the checkpoint count from heimdall +func (h *HeimdallClient) FetchCheckpointCount(ctx context.Context) (int64, error) { + url, err := checkpointCountURL(h.urlString) + if err != nil { + return 0, err + } + + ctx = withRequestType(ctx, checkpointCountRequest) + + response, err := FetchWithRetry[checkpoint.CheckpointCountResponse](ctx, h.client, url, h.closeCh) + if err != nil { + return 0, err + } + + return response.Result.Result, nil +} + +// FetchWithRetry returns data from heimdall with retry +func FetchWithRetry[T any](ctx context.Context, client http.Client, url *url.URL, closeCh chan struct{}) (*T, error) { + // request data once + request := &Request{client: client, url: url, start: time.Now()} + result, err := Fetch[T](ctx, request) + + if err == nil { + return result, nil + } + + // attempt counter + attempt := 1 + + log.Warn("an error while trying fetching from Heimdall", "attempt", attempt, "error", err) + + // create a new ticker for retrying the request + ticker := time.NewTicker(retryCall) + defer ticker.Stop() + + const logEach = 5 + +retryLoop: + for { + log.Info("Retrying again in 5 seconds to fetch data from Heimdall", "path", url.Path, "attempt", attempt) + + attempt++ + + select { + case <-ctx.Done(): + log.Debug("Shutdown detected, terminating request by context.Done") + + return nil, ctx.Err() + case <-closeCh: + log.Debug("Shutdown detected, terminating request by closing") + + return nil, ErrShutdownDetected + case <-ticker.C: + request = &Request{client: client, url: url, start: time.Now()} + result, err = Fetch[T](ctx, request) + + if err != nil { + if attempt%logEach == 0 { + log.Warn("an error while trying fetching from Heimdall", "attempt", attempt, "error", err) + } + + continue retryLoop + } + + return result, nil + } + } +} + +// TODO: Uncomment once metrics are added +// Fetch fetches response from heimdall +func Fetch[T any](ctx context.Context, request *Request) (*T, error) { + // isSuccessful := false + + // defer func() { + // if metrics.EnabledExpensive { + // sendMetrics(ctx, request.start, isSuccessful) + // } + // }() + + result := new(T) + + body, err := internalFetchWithTimeout(ctx, request.client, request.url) + if err != nil { + return nil, err + } + + if body == nil { + return nil, ErrNoResponse + } + + err = json.Unmarshal(body, result) + if err != nil { + return nil, err + } + + // isSuccessful = true + + return result, nil +} + +func spanURL(urlString string, spanID uint64) (*url.URL, error) { + return makeURL(urlString, fmt.Sprintf(fetchSpanFormat, spanID), "") +} + +func stateSyncURL(urlString string, fromID uint64, to int64) (*url.URL, error) { + queryParams := fmt.Sprintf(fetchStateSyncEventsFormat, fromID, to, stateFetchLimit) + + return makeURL(urlString, fetchStateSyncEventsPath, queryParams) +} + +func checkpointURL(urlString string, number int64) (*url.URL, error) { + url := "" + if number == -1 { + url = fmt.Sprintf(fetchCheckpoint, "latest") + } else { + url = fmt.Sprintf(fetchCheckpoint, fmt.Sprint(number)) + } + + return makeURL(urlString, url, "") +} + +func checkpointCountURL(urlString string) (*url.URL, error) { + return makeURL(urlString, fetchCheckpointCount, "") +} + +func makeURL(urlString, rawPath, rawQuery string) (*url.URL, error) { + u, err := url.Parse(urlString) + if err != nil { + return nil, err + } + + u.Path = rawPath + u.RawQuery = rawQuery + + return u, err +} + +// internal fetch method +func internalFetch(ctx context.Context, client http.Client, u *url.URL) ([]byte, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return nil, err + } + + res, err := client.Do(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + // check status code + if res.StatusCode != 200 && res.StatusCode != 204 { + return nil, fmt.Errorf("%w: response code %d", ErrNotSuccessfulResponse, res.StatusCode) + } + + // unmarshall data from buffer + if res.StatusCode == 204 { + return nil, nil + } + + // get response + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + return body, nil +} + +func internalFetchWithTimeout(ctx context.Context, client http.Client, url *url.URL) ([]byte, error) { + ctx, cancel := context.WithTimeout(ctx, apiHeimdallTimeout) + defer cancel() + + // request data once + return internalFetch(ctx, client, url) +} + +// Close sends a signal to stop the running process +func (h *HeimdallClient) Close() { + close(h.closeCh) + h.client.CloseIdleConnections() +} diff --git a/consensus/bor/heimdall/metrics.go b/consensus/bor/heimdall/metrics.go new file mode 100644 index 00000000000..ca7f190035f --- /dev/null +++ b/consensus/bor/heimdall/metrics.go @@ -0,0 +1,83 @@ +package heimdall + +import ( + "context" + "time" +) + +type ( + requestTypeKey struct{} + requestType string + + // TODO: Uncomment once metrics are added + // meter struct { + // request map[bool]metrics.Meter // map[isSuccessful]metrics.Meter + // timer metrics.Timer + // } +) + +const ( + stateSyncRequest requestType = "state-sync" + spanRequest requestType = "span" + checkpointRequest requestType = "checkpoint" + checkpointCountRequest requestType = "checkpoint-count" +) + +func withRequestType(ctx context.Context, reqType requestType) context.Context { + return context.WithValue(ctx, requestTypeKey{}, reqType) +} + +func getRequestType(ctx context.Context) (requestType, bool) { + reqType, ok := ctx.Value(requestTypeKey{}).(requestType) + return reqType, ok +} + +// TODO: Uncomment once metrics are added +// var ( +// requestMeters = map[requestType]meter{ +// stateSyncRequest: { +// request: map[bool]metrics.Meter{ +// true: metrics.NewRegisteredMeter("client/requests/statesync/valid", nil), +// false: metrics.NewRegisteredMeter("client/requests/statesync/invalid", nil), +// }, +// timer: metrics.NewRegisteredTimer("client/requests/statesync/duration", nil), +// }, +// spanRequest: { +// request: map[bool]metrics.Meter{ +// true: metrics.NewRegisteredMeter("client/requests/span/valid", nil), +// false: metrics.NewRegisteredMeter("client/requests/span/invalid", nil), +// }, +// timer: metrics.NewRegisteredTimer("client/requests/span/duration", nil), +// }, +// checkpointRequest: { +// request: map[bool]metrics.Meter{ +// true: metrics.NewRegisteredMeter("client/requests/checkpoint/valid", nil), +// false: metrics.NewRegisteredMeter("client/requests/checkpoint/invalid", nil), +// }, +// timer: metrics.NewRegisteredTimer("client/requests/checkpoint/duration", nil), +// }, +// checkpointCountRequest: { +// request: map[bool]metrics.Meter{ +// true: metrics.NewRegisteredMeter("client/requests/checkpointcount/valid", nil), +// false: metrics.NewRegisteredMeter("client/requests/checkpointcount/invalid", nil), +// }, +// timer: metrics.NewRegisteredTimer("client/requests/checkpointcount/duration", nil), +// }, +// } +// ) + +// TODO: Uncomment once metrics is added +func sendMetrics(ctx context.Context, start time.Time, isSuccessful bool) { + // reqType, ok := getRequestType(ctx) + // if !ok { + // return + // } + + // meters, ok := requestMeters[reqType] + // if !ok { + // return + // } + + // meters.request[isSuccessful].Mark(1) + // meters.timer.Update(time.Since(start)) +} diff --git a/consensus/bor/heimdall/span/span.go b/consensus/bor/heimdall/span/span.go new file mode 100644 index 00000000000..22d3dff2563 --- /dev/null +++ b/consensus/bor/heimdall/span/span.go @@ -0,0 +1,30 @@ +package span + +import ( + "github.com/google/btree" + "github.com/ledgerwatch/erigon/consensus/bor/valset" +) + +// 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 valset.ValidatorSet `json:"validator_set" yaml:"validator_set"` + SelectedProducers []valset.Validator `json:"selected_producers" yaml:"selected_producers"` + ChainID string `json:"bor_chain_id" yaml:"bor_chain_id"` +} + +func (hs *HeimdallSpan) Less(other btree.Item) bool { + otherHs := other.(*HeimdallSpan) + if hs.EndBlock == 0 || otherHs.EndBlock == 0 { + // if endblock is not specified in one of the items, allow search by ID + return hs.ID < otherHs.ID + } + return hs.EndBlock < otherHs.EndBlock +} diff --git a/consensus/bor/heimdall/span/spanner.go b/consensus/bor/heimdall/span/spanner.go new file mode 100644 index 00000000000..b978294e119 --- /dev/null +++ b/consensus/bor/heimdall/span/spanner.go @@ -0,0 +1,139 @@ +package span + +import ( + "encoding/hex" + "math/big" + + "github.com/ledgerwatch/erigon-lib/chain" + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/accounts/abi" + "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/consensus/bor/valset" + "github.com/ledgerwatch/erigon/params/networkname" + "github.com/ledgerwatch/erigon/rlp" + "github.com/ledgerwatch/log/v3" +) + +type ChainSpanner struct { + validatorSet abi.ABI + chainConfig *chain.Config +} + +func NewChainSpanner(validatorSet abi.ABI, chainConfig *chain.Config) *ChainSpanner { + return &ChainSpanner{ + validatorSet: validatorSet, + chainConfig: chainConfig, + } +} + +// GetCurrentSpan get current span from contract +func (c *ChainSpanner) GetCurrentSpan(syscall consensus.SystemCall) (*Span, error) { + + // method + const method = "getCurrentSpan" + + data, err := c.validatorSet.Pack(method) + if err != nil { + log.Error("Unable to pack tx for getCurrentSpan", "error", err) + return nil, err + } + + result, err := syscall(libcommon.HexToAddress(c.chainConfig.Bor.ValidatorContract), data) + if err != nil { + return nil, err + } + + // span result + ret := new(struct { + Number *big.Int + StartBlock *big.Int + EndBlock *big.Int + }) + + if err := c.validatorSet.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 +} + +func (c *ChainSpanner) GetCurrentValidators(blockNumber uint64, signer libcommon.Address, getSpanForBlock func(blockNum uint64) (*HeimdallSpan, error)) ([]*valset.Validator, error) { + // Use signer as validator in case of bor devent + if c.chainConfig.ChainName == networkname.BorDevnetChainName { + validators := []*valset.Validator{ + { + ID: 1, + Address: signer, + VotingPower: 1000, + ProposerPriority: 1, + }, + } + + return validators, nil + } + + span, err := getSpanForBlock(blockNumber) + if err != nil { + return nil, err + } + + return span.ValidatorSet.Validators, nil +} + +func (c *ChainSpanner) CommitSpan(heimdallSpan HeimdallSpan, syscall consensus.SystemCall) error { + + // method + const method = "commitSpan" + + // get validators bytes + validators := make([]valset.MinimalVal, 0, len(heimdallSpan.ValidatorSet.Validators)) + for _, val := range heimdallSpan.ValidatorSet.Validators { + validators = append(validators, val.MinimalVal()) + } + validatorBytes, err := rlp.EncodeToBytes(validators) + if err != nil { + return err + } + + // get producers bytes + producers := make([]valset.MinimalVal, 0, len(heimdallSpan.SelectedProducers)) + for _, val := range heimdallSpan.SelectedProducers { + producers = append(producers, val.MinimalVal()) + } + producerBytes, err := rlp.EncodeToBytes(producers) + if err != nil { + return err + } + + log.Debug("✅ 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.validatorSet.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 + } + + _, err = syscall(libcommon.HexToAddress(c.chainConfig.Bor.ValidatorContract), data) + + return err +} diff --git a/consensus/bor/heimdallgrpc/checkpoint.go b/consensus/bor/heimdallgrpc/checkpoint.go new file mode 100644 index 00000000000..c2253aedaad --- /dev/null +++ b/consensus/bor/heimdallgrpc/checkpoint.go @@ -0,0 +1,51 @@ +package heimdallgrpc + +import ( + "context" + "math/big" + + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/checkpoint" + "github.com/ledgerwatch/log/v3" + + proto "github.com/maticnetwork/polyproto/heimdall" + protoutils "github.com/maticnetwork/polyproto/utils" +) + +func (h *HeimdallGRPCClient) FetchCheckpointCount(ctx context.Context) (int64, error) { + log.Info("Fetching checkpoint count") + + res, err := h.client.FetchCheckpointCount(ctx, nil) + if err != nil { + return 0, err + } + + log.Info("Fetched checkpoint count") + + return res.Result.Result, nil +} + +func (h *HeimdallGRPCClient) FetchCheckpoint(ctx context.Context, number int64) (*checkpoint.Checkpoint, error) { + req := &proto.FetchCheckpointRequest{ + ID: number, + } + + log.Info("Fetching checkpoint", "number", number) + + res, err := h.client.FetchCheckpoint(ctx, req) + if err != nil { + return nil, err + } + + log.Info("Fetched checkpoint", "number", number) + + checkpoint := &checkpoint.Checkpoint{ + StartBlock: new(big.Int).SetUint64(res.Result.StartBlock), + EndBlock: new(big.Int).SetUint64(res.Result.EndBlock), + RootHash: protoutils.ConvertH256ToHash(res.Result.RootHash), + Proposer: protoutils.ConvertH160toAddress(res.Result.Proposer), + BorChainID: res.Result.BorChainID, + Timestamp: uint64(res.Result.Timestamp.GetSeconds()), + } + + return checkpoint, nil +} diff --git a/consensus/bor/heimdallgrpc/client.go b/consensus/bor/heimdallgrpc/client.go new file mode 100644 index 00000000000..d41ca84e4ca --- /dev/null +++ b/consensus/bor/heimdallgrpc/client.go @@ -0,0 +1,51 @@ +package heimdallgrpc + +import ( + "time" + + grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" + "github.com/ledgerwatch/log/v3" + proto "github.com/maticnetwork/polyproto/heimdall" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" +) + +const ( + stateFetchLimit = 50 +) + +type HeimdallGRPCClient struct { + conn *grpc.ClientConn + client proto.HeimdallClient +} + +func NewHeimdallGRPCClient(address string) *HeimdallGRPCClient { + opts := []grpc_retry.CallOption{ + grpc_retry.WithMax(10000), + grpc_retry.WithBackoff(grpc_retry.BackoffLinear(5 * time.Second)), + grpc_retry.WithCodes(codes.Internal, codes.Unavailable, codes.Aborted, codes.NotFound), + } + + conn, err := grpc.Dial(address, + grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(opts...)), + grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(opts...)), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + log.Crit("Failed to connect to Heimdall gRPC", "error", err) + } + + log.Info("Connected to Heimdall gRPC server", "address", address) + + return &HeimdallGRPCClient{ + conn: conn, + client: proto.NewHeimdallClient(conn), + } +} + +func (h *HeimdallGRPCClient) Close() { + log.Debug("Shutdown detected, Closing Heimdall gRPC client") + h.conn.Close() +} diff --git a/consensus/bor/heimdallgrpc/span.go b/consensus/bor/heimdallgrpc/span.go new file mode 100644 index 00000000000..d4861b984d1 --- /dev/null +++ b/consensus/bor/heimdallgrpc/span.go @@ -0,0 +1,63 @@ +package heimdallgrpc + +import ( + "context" + + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" + "github.com/ledgerwatch/erigon/consensus/bor/valset" + "github.com/ledgerwatch/log/v3" + + proto "github.com/maticnetwork/polyproto/heimdall" + protoutils "github.com/maticnetwork/polyproto/utils" +) + +func (h *HeimdallGRPCClient) Span(ctx context.Context, spanID uint64) (*span.HeimdallSpan, error) { + req := &proto.SpanRequest{ + ID: spanID, + } + + log.Info("Fetching span", "spanID", spanID) + + res, err := h.client.Span(ctx, req) + if err != nil { + return nil, err + } + + log.Info("Fetched span", "spanID", spanID) + + return parseSpan(res.Result), nil +} + +func parseSpan(protoSpan *proto.Span) *span.HeimdallSpan { + resp := &span.HeimdallSpan{ + Span: span.Span{ + ID: protoSpan.ID, + StartBlock: protoSpan.StartBlock, + EndBlock: protoSpan.EndBlock, + }, + ValidatorSet: valset.ValidatorSet{}, + SelectedProducers: []valset.Validator{}, + ChainID: protoSpan.ChainID, + } + + for _, validator := range protoSpan.ValidatorSet.Validators { + resp.ValidatorSet.Validators = append(resp.ValidatorSet.Validators, parseValidator(validator)) + } + + resp.ValidatorSet.Proposer = parseValidator(protoSpan.ValidatorSet.Proposer) + + for _, validator := range protoSpan.SelectedProducers { + resp.SelectedProducers = append(resp.SelectedProducers, *parseValidator(validator)) + } + + return resp +} + +func parseValidator(validator *proto.Validator) *valset.Validator { + return &valset.Validator{ + ID: validator.ID, + Address: protoutils.ConvertH160toAddress(validator.Address), + VotingPower: validator.VotingPower, + ProposerPriority: validator.ProposerPriority, + } +} diff --git a/consensus/bor/heimdallgrpc/state_sync.go b/consensus/bor/heimdallgrpc/state_sync.go new file mode 100644 index 00000000000..3cf93dc906a --- /dev/null +++ b/consensus/bor/heimdallgrpc/state_sync.go @@ -0,0 +1,59 @@ +package heimdallgrpc + +import ( + "context" + "errors" + "io" + + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/consensus/bor/clerk" + proto "github.com/maticnetwork/polyproto/heimdall" +) + +func (h *HeimdallGRPCClient) StateSyncEvents(ctx context.Context, fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) { + eventRecords := make([]*clerk.EventRecordWithTime, 0) + + req := &proto.StateSyncEventsRequest{ + FromID: fromID, + ToTime: uint64(to), + Limit: uint64(stateFetchLimit), + } + + var ( + res proto.Heimdall_StateSyncEventsClient + events *proto.StateSyncEventsResponse + err error + ) + + res, err = h.client.StateSyncEvents(ctx, req) + if err != nil { + return nil, err + } + + for { + events, err = res.Recv() + if errors.Is(err, io.EOF) { + return eventRecords, nil + } + + if err != nil { + return nil, err + } + + for _, event := range events.Result { + eventRecord := &clerk.EventRecordWithTime{ + EventRecord: clerk.EventRecord{ + ID: event.ID, + Contract: libcommon.HexToAddress(event.Contract), + Data: common.Hex2Bytes(event.Data[2:]), + TxHash: libcommon.HexToHash(event.TxHash), + LogIndex: event.LogIndex, + ChainID: event.ChainID, + }, + Time: event.Time.AsTime(), + } + eventRecords = append(eventRecords, eventRecord) + } + } +} diff --git a/consensus/bor/merkle.go b/consensus/bor/merkle.go index ba00bc53e8f..1e0114acdef 100644 --- a/consensus/bor/merkle.go +++ b/consensus/bor/merkle.go @@ -2,12 +2,14 @@ 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 } @@ -24,6 +26,7 @@ func NextPowerOfTwo(n uint64) uint64 { n |= n >> 16 n |= n >> 32 n++ + return n } @@ -32,17 +35,21 @@ func ConvertTo32(input []byte) (output [32]byte, err error) { if l > 32 || l == 0 { return } + copy(output[32-l:], input) + return } func Convert(input [][32]byte) [][]byte { output := make([][]byte, 0, len(input)) + 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 deleted file mode 100644 index 5b133aba0af..00000000000 --- a/consensus/bor/rest.go +++ /dev/null @@ -1,148 +0,0 @@ -package bor - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "sort" - "time" - - "github.com/ledgerwatch/log/v3" -) - -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(ctx context.Context, path string, query string) (*ResponseWithHeight, error) - FetchWithRetry(ctx context.Context, path string, query string) (*ResponseWithHeight, error) - FetchStateSyncEvents(ctx context.Context, 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: 5 * time.Second, - }, - } - return h, nil -} - -func (h *HeimdallClient) FetchStateSyncEvents(ctx context.Context, 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.Trace("Fetching state sync events", "queryParams", queryParams) - response, err := h.FetchWithRetry(ctx, "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(ctx context.Context, 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(ctx, u) -} - -// FetchWithRetry returns data from heimdall with retry -func (h *HeimdallClient) FetchWithRetry(ctx context.Context, 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(ctx, u) - if err == nil && res != nil { - return res, nil - } - log.Info("Retrying again in 5 seconds for next Heimdall span", "path", u.Path) - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-time.After(5 * time.Second): - } - } -} - -// internal fetch method -func (h *HeimdallClient) internalFetch(ctx context.Context, u *url.URL) (*ResponseWithHeight, error) { - req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) - if err != nil { - return nil, err - } - res, err := h.client.Do(req) - 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 := io.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 index 0626063b608..e900b3fa682 100644 --- a/consensus/bor/snapshot.go +++ b/consensus/bor/snapshot.go @@ -9,7 +9,7 @@ import ( "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/kv" - + "github.com/ledgerwatch/erigon/consensus/bor/valset" "github.com/ledgerwatch/erigon/core/types" ) @@ -20,7 +20,7 @@ type Snapshot struct { Number uint64 `json:"number"` // Block number where the snapshot was created Hash libcommon.Hash `json:"hash"` // Block hash where the snapshot was created - ValidatorSet *ValidatorSet `json:"validatorSet"` // Validator set at this moment + ValidatorSet *valset.ValidatorSet `json:"validatorSet"` // Validator set at this moment Recents map[uint64]libcommon.Address `json:"recents"` // Set of recent signers for spam protections } @@ -41,14 +41,14 @@ func newSnapshot( sigcache *lru.ARCCache, number uint64, hash libcommon.Hash, - validators []*Validator, + validators []*valset.Validator, ) *Snapshot { snap := &Snapshot{ config: config, sigcache: sigcache, Number: number, Hash: hash, - ValidatorSet: NewValidatorSet(validators), + ValidatorSet: valset.NewValidatorSet(validators), Recents: make(map[uint64]libcommon.Address), } return snap @@ -60,20 +60,25 @@ func loadSnapshot(config *chain.BorConfig, sigcache *lru.ARCCache, db kv.RwDB, h if err != nil { return nil, err } + defer tx.Rollback() + blob, err := tx.GetOne(kv.BorSeparate, 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 // update total voting power - if err := snap.ValidatorSet.updateTotalVotingPower(); err != nil { + if err := snap.ValidatorSet.UpdateTotalVotingPower(); err != nil { return nil, err } @@ -120,6 +125,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { return nil, errOutOfRangeChain } } + if headers[0].Number.Uint64() != s.Number+1 { return nil, errOutOfRangeChain } @@ -142,7 +148,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { } // check if signer is in validator set - if !snap.ValidatorSet.HasAddress(signer.Bytes()) { + if !snap.ValidatorSet.HasAddress(signer) { return nil, &UnauthorizedSignerError{number, signer.Bytes()} } @@ -161,12 +167,13 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { validatorBytes := header.Extra[extraVanity : len(header.Extra)-extraSeal] // get validators from headers and use that for new validator set - newVals, _ := ParseValidators(validatorBytes) + newVals, _ := valset.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() @@ -178,10 +185,13 @@ func (s *Snapshot) GetSignerSuccessionNumber(signer libcommon.Address) (int, err 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()} } @@ -192,6 +202,7 @@ func (s *Snapshot) GetSignerSuccessionNumber(signer libcommon.Address) (int, err tempIndex = tempIndex + len(validators) } } + return tempIndex - proposerIndex, nil } @@ -201,6 +212,7 @@ func (s *Snapshot) signers() []libcommon.Address { for _, sig := range s.ValidatorSet.Validators { sigs = append(sigs, sig.Address) } + return sigs } diff --git a/consensus/bor/snapshot_test.go b/consensus/bor/snapshot_test.go index e820ca554c7..ac32b33ac1e 100644 --- a/consensus/bor/snapshot_test.go +++ b/consensus/bor/snapshot_test.go @@ -7,7 +7,8 @@ import ( "time" libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/stretchr/testify/assert" + "github.com/ledgerwatch/erigon/consensus/bor/valset" + "github.com/stretchr/testify/require" ) const ( @@ -15,8 +16,10 @@ const ( ) func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) { + t.Parallel() + validators := buildRandomValidatorSet(numVals) - validatorSet := NewValidatorSet(validators) + validatorSet := valset.NewValidatorSet(validators) snap := Snapshot{ ValidatorSet: validatorSet, } @@ -27,20 +30,24 @@ func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) { if err != nil { t.Fatalf("%s", err) } - assert.Equal(t, 0, successionNumber) + + require.Equal(t, 0, successionNumber) } func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) { + t.Parallel() + validators := buildRandomValidatorSet(numVals) // sort validators by address, which is what NewValidatorSet also does - sort.Sort(ValidatorsByAddress(validators)) + sort.Sort(valset.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), + ValidatorSet: valset.NewValidatorSet(validators), } // choose a signer at an index greater than proposer index @@ -49,17 +56,20 @@ func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) { if err != nil { t.Fatalf("%s", err) } - assert.Equal(t, signerIndex-proposerIndex, successionNumber) + + require.Equal(t, signerIndex-proposerIndex, successionNumber) } func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) { + t.Parallel() + 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), + ValidatorSet: valset.NewValidatorSet(validators), } // choose a signer at an index greater than proposer index @@ -68,43 +78,54 @@ func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) { if err != nil { t.Fatalf("%s", err) } - assert.Equal(t, signerIndex+numVals-proposerIndex, successionNumber) + + require.Equal(t, signerIndex+numVals-proposerIndex, successionNumber) } func TestGetSignerSuccessionNumber_ProposerNotFound(t *testing.T) { + t.Parallel() + validators := buildRandomValidatorSet(numVals) snap := Snapshot{ - ValidatorSet: NewValidatorSet(validators), + ValidatorSet: valset.NewValidatorSet(validators), } + dummyProposerAddress := randomAddress() - snap.ValidatorSet.Proposer = &Validator{Address: dummyProposerAddress} + snap.ValidatorSet.Proposer = &valset.Validator{Address: dummyProposerAddress} + // choose any signer signer := snap.ValidatorSet.Validators[3].Address + _, err := snap.GetSignerSuccessionNumber(signer) - assert.NotNil(t, err) + require.NotNil(t, err) + e, ok := err.(*UnauthorizedProposerError) - assert.True(t, ok) - assert.Equal(t, dummyProposerAddress.Bytes(), e.Proposer) + require.True(t, ok) + require.Equal(t, dummyProposerAddress.Bytes(), e.Proposer) } func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) { + t.Parallel() + validators := buildRandomValidatorSet(numVals) snap := Snapshot{ - ValidatorSet: NewValidatorSet(validators), + ValidatorSet: valset.NewValidatorSet(validators), } + dummySignerAddress := randomAddress() _, err := snap.GetSignerSuccessionNumber(dummySignerAddress) - assert.NotNil(t, err) + require.NotNil(t, err) + e, ok := err.(*UnauthorizedSignerError) - assert.True(t, ok) - assert.Equal(t, dummySignerAddress.Bytes(), e.Signer) + require.True(t, ok) + require.Equal(t, dummySignerAddress.Bytes(), e.Signer) } -func buildRandomValidatorSet(numVals int) []*Validator { +func buildRandomValidatorSet(numVals int) []*valset.Validator { rand.Seed(time.Now().Unix()) - validators := make([]*Validator, numVals) + validators := make([]*valset.Validator, numVals) for i := 0; i < numVals; i++ { - validators[i] = &Validator{ + validators[i] = &valset.Validator{ Address: randomAddress(), // cannot process validators with voting power 0, hence +1 VotingPower: int64(rand.Intn(99) + 1), @@ -112,7 +133,7 @@ func buildRandomValidatorSet(numVals int) []*Validator { } // sort validators by address, which is what NewValidatorSet also does - sort.Sort(ValidatorsByAddress(validators)) + sort.Sort(valset.ValidatorsByAddress(validators)) return validators } diff --git a/consensus/bor/span.go b/consensus/bor/span.go index 3c9887a774c..95a3c40ea42 100644 --- a/consensus/bor/span.go +++ b/consensus/bor/span.go @@ -1,29 +1,15 @@ package bor import ( - "github.com/google/btree" + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" + "github.com/ledgerwatch/erigon/consensus/bor/valset" ) -// 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"` -} - -func (hs *HeimdallSpan) Less(other btree.Item) bool { - otherHs := other.(*HeimdallSpan) - if hs.EndBlock == 0 || otherHs.EndBlock == 0 { - // if endblock is not specified in one of the items, allow search by ID - return hs.ID < otherHs.ID - } - return hs.EndBlock < otherHs.EndBlock +//go:generate mockgen -destination=./span_mock.go -package=bor . Spanner +type Spanner interface { + GetCurrentSpan(syscall consensus.SystemCall) (*span.Span, error) + GetCurrentValidators(blockNumber uint64, signer libcommon.Address, getSpanForBlock func(blockNum uint64) (*span.HeimdallSpan, error)) ([]*valset.Validator, error) + CommitSpan(heimdallSpan span.HeimdallSpan, syscall consensus.SystemCall) error } diff --git a/consensus/bor/span_mock.go b/consensus/bor/span_mock.go new file mode 100644 index 00000000000..937f52a4419 --- /dev/null +++ b/consensus/bor/span_mock.go @@ -0,0 +1,82 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ledgerwatch/erigon/consensus/bor (interfaces: Spanner) + +// Package bor is a generated GoMock package. +package bor + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + common "github.com/ledgerwatch/erigon-lib/common" + consensus "github.com/ledgerwatch/erigon/consensus" + span "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" + valset "github.com/ledgerwatch/erigon/consensus/bor/valset" +) + +// MockSpanner is a mock of Spanner interface. +type MockSpanner struct { + ctrl *gomock.Controller + recorder *MockSpannerMockRecorder +} + +// MockSpannerMockRecorder is the mock recorder for MockSpanner. +type MockSpannerMockRecorder struct { + mock *MockSpanner +} + +// NewMockSpanner creates a new mock instance. +func NewMockSpanner(ctrl *gomock.Controller) *MockSpanner { + mock := &MockSpanner{ctrl: ctrl} + mock.recorder = &MockSpannerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSpanner) EXPECT() *MockSpannerMockRecorder { + return m.recorder +} + +// CommitSpan mocks base method. +func (m *MockSpanner) CommitSpan(arg0 span.HeimdallSpan, arg1 consensus.SystemCall) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitSpan", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitSpan indicates an expected call of CommitSpan. +func (mr *MockSpannerMockRecorder) CommitSpan(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitSpan", reflect.TypeOf((*MockSpanner)(nil).CommitSpan), arg0, arg1) +} + +// GetCurrentSpan mocks base method. +func (m *MockSpanner) GetCurrentSpan(arg0 consensus.SystemCall) (*span.Span, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCurrentSpan", arg0) + ret0, _ := ret[0].(*span.Span) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCurrentSpan indicates an expected call of GetCurrentSpan. +func (mr *MockSpannerMockRecorder) GetCurrentSpan(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentSpan", reflect.TypeOf((*MockSpanner)(nil).GetCurrentSpan), arg0) +} + +// GetCurrentValidators mocks base method. +func (m *MockSpanner) GetCurrentValidators(arg0 uint64, arg1 common.Address, arg2 func(uint64) (*span.HeimdallSpan, error)) ([]*valset.Validator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCurrentValidators", arg0, arg1, arg2) + ret0, _ := ret[0].([]*valset.Validator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCurrentValidators indicates an expected call of GetCurrentValidators. +func (mr *MockSpannerMockRecorder) GetCurrentValidators(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidators", reflect.TypeOf((*MockSpanner)(nil).GetCurrentValidators), arg0, arg1, arg2) +} diff --git a/consensus/bor/statefull/processor.go b/consensus/bor/statefull/processor.go new file mode 100644 index 00000000000..b822914f8ca --- /dev/null +++ b/consensus/bor/statefull/processor.go @@ -0,0 +1,20 @@ +package statefull + +import ( + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/core/types" +) + +type ChainContext struct { + Chain consensus.ChainHeaderReader + Bor consensus.Engine +} + +func (c ChainContext) Engine() consensus.Engine { + return c.Bor +} + +func (c ChainContext) GetHeader(hash libcommon.Hash, number uint64) *types.Header { + return c.Chain.GetHeader(hash, number) +} diff --git a/consensus/bor/valset/error.go b/consensus/bor/valset/error.go new file mode 100644 index 00000000000..37add216834 --- /dev/null +++ b/consensus/bor/valset/error.go @@ -0,0 +1,32 @@ +package valset + +import "fmt" + +// 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, + ) +} diff --git a/consensus/bor/validator.go b/consensus/bor/valset/validator.go similarity index 93% rename from consensus/bor/validator.go rename to consensus/bor/valset/validator.go index c97f109bf67..2a525bfd935 100644 --- a/consensus/bor/validator.go +++ b/consensus/bor/valset/validator.go @@ -1,4 +1,4 @@ -package bor +package valset import ( "bytes" @@ -43,29 +43,38 @@ func (v *Validator) Cmp(other *Validator) *Validator { if v == nil { return other } + if other == nil { return v } + if v.ProposerPriority > other.ProposerPriority { return v - } else if v.ProposerPriority < other.ProposerPriority { + } + + 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") - } } + + result := bytes.Compare(v.Address.Bytes(), other.Address.Bytes()) + + if result == 0 { + panic("Cannot compare identical validators") + } + + if result < 0 { + return v + } + + // result > 0 + return other } 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, @@ -87,6 +96,7 @@ func (v *Validator) HeaderBytes() []byte { result := make([]byte, 40) copy(result[:20], v.Address.Bytes()) copy(result[20:], v.PowerBytes()) + return result } @@ -95,6 +105,7 @@ 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 } @@ -114,6 +125,7 @@ func ParseValidators(validatorsBytes []byte) ([]*Validator, error) { } result := make([]*Validator, len(validatorsBytes)/40) + for i := 0; i < len(validatorsBytes); i += 40 { address := make([]byte, 20) power := make([]byte, 20) @@ -142,6 +154,7 @@ 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 } @@ -150,5 +163,6 @@ 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/valset/validator_set.go similarity index 93% rename from consensus/bor/validator_set.go rename to consensus/bor/valset/validator_set.go index 5c05fa4251e..0f4cdf115ef 100644 --- a/consensus/bor/validator_set.go +++ b/consensus/bor/valset/validator_set.go @@ -1,4 +1,4 @@ -package bor +package valset // Tendermint leader selection algorithm @@ -46,6 +46,7 @@ type ValidatorSet struct { // cached (unexported) totalVotingPower int64 + validatorsMap map[libcommon.Address]int // address -> index } // NewValidatorSet initializes a ValidatorSet by copying over the @@ -55,13 +56,16 @@ type ValidatorSet struct { // 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 } @@ -72,9 +76,10 @@ func (vals *ValidatorSet) IsNilOrEmpty() bool { // 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 + validatorCopy := vals.Copy() + validatorCopy.IncrementProposerPriority(times) + + return validatorCopy } // IncrementProposerPriority increments ProposerPriority of each validator and updates the @@ -84,6 +89,7 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) { if vals.IsNilOrEmpty() { panic("empty validator set") } + if times <= 0 { panic("Cannot call IncrementProposerPriority with non-positive times") } @@ -120,6 +126,7 @@ func (vals *ValidatorSet) RescalePriorities(diffMax int64) { // 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 @@ -145,10 +152,13 @@ func (vals *ValidatorSet) incrementProposerPriority() *Validator { 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() } @@ -162,17 +172,22 @@ 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 { @@ -185,6 +200,7 @@ func (vals *ValidatorSet) getValWithMostPriority() *Validator { for _, val := range vals.Validators { res = res.Cmp(val) } + return res } @@ -192,7 +208,9 @@ 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) } @@ -203,40 +221,49 @@ 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 { + valCopy := validatorListCopy(vals.Validators) + validatorsMap := make(map[libcommon.Address]int, len(vals.Validators)) + + for i, val := range valCopy { + validatorsMap[val.Address] = i + } + return &ValidatorSet{ Validators: validatorListCopy(vals.Validators), Proposer: vals.Proposer, totalVotingPower: vals.totalVotingPower, + validatorsMap: validatorsMap, } } // 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) +func (vals *ValidatorSet) HasAddress(address libcommon.Address) bool { + _, ok := vals.validatorsMap[address] + + return ok } // 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 libcommon.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()) { + idx, ok := vals.validatorsMap[address] + if ok { return idx, vals.Validators[idx].Copy() } + return -1, nil } @@ -247,7 +274,9 @@ 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() } @@ -257,8 +286,7 @@ func (vals *ValidatorSet) Size() int { } // Force recalculation of the set's total voting power. -func (vals *ValidatorSet) updateTotalVotingPower() error { - +func (vals *ValidatorSet) UpdateTotalVotingPower() error { sum := int64(0) for _, val := range vals.Validators { // mind overflow @@ -267,7 +295,9 @@ func (vals *ValidatorSet) updateTotalVotingPower() error { return &TotalVotingPowerExceededError{sum, vals.Validators} } } + vals.totalVotingPower = sum + return nil } @@ -276,11 +306,13 @@ func (vals *ValidatorSet) updateTotalVotingPower() error { func (vals *ValidatorSet) TotalVotingPower() int64 { if vals.totalVotingPower == 0 { log.Info("invoking updateTotalVotingPower before returning it") - if err := vals.updateTotalVotingPower(); err != nil { + + if err := vals.UpdateTotalVotingPower(); err != nil { // Can/should we do better? panic(err) } } + return vals.totalVotingPower } @@ -290,9 +322,11 @@ 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() } @@ -303,6 +337,7 @@ func (vals *ValidatorSet) findProposer() *Validator { proposer = proposer.Cmp(val) } } + return proposer } @@ -341,8 +376,14 @@ func processChanges(origChanges []*Validator) (updates, removals []*Validator, e changes := validatorListCopy(origChanges) sort.Sort(ValidatorsByAddress(changes)) - removals = make([]*Validator, 0, len(changes)) - updates = make([]*Validator, 0, len(changes)) + sliceCap := len(changes) / 2 + if sliceCap == 0 { + sliceCap = 1 + } + + removals = make([]*Validator, 0, sliceCap) + updates = make([]*Validator, 0, sliceCap) + var prevAddr libcommon.Address // Scan changes by address and append valid validators to updates or removals lists. @@ -351,22 +392,27 @@ func processChanges(origChanges []*Validator) (updates, removals []*Validator, e 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 } @@ -382,12 +428,12 @@ func processChanges(origChanges []*Validator) (updates, removals []*Validator, e // 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 @@ -396,7 +442,9 @@ func verifyUpdates(updates []*Validator, vals *ValidatorSet) (updatedTotalVoting // 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", @@ -414,10 +462,10 @@ func verifyUpdates(updates []*Validator, vals *ValidatorSet) (updatedTotalVoting // '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 @@ -440,7 +488,6 @@ func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotal // 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 @@ -456,6 +503,7 @@ func (vals *ValidatorSet) applyUpdates(updates []*Validator) { // Validator is present in both, advance existing. existing = existing[1:] } + updates = updates[1:] } i++ @@ -466,6 +514,7 @@ func (vals *ValidatorSet) applyUpdates(updates []*Validator) { merged[i] = existing[j] i++ } + // OR add updates which are left. for j := 0; j < len(updates); j++ { merged[i] = updates[j] @@ -478,24 +527,25 @@ func (vals *ValidatorSet) applyUpdates(updates []*Validator) { // 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)) @@ -509,6 +559,7 @@ func (vals *ValidatorSet) applyRemovals(deletes []*Validator) { merged[i] = existing[0] i++ } + existing = existing[1:] } @@ -526,7 +577,6 @@ func (vals *ValidatorSet) applyRemovals(deletes []*Validator) { // 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) < 1 { return nil } @@ -561,10 +611,9 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes computeNewPriorities(updates, vals, updatedTotalVotingPower) // Apply updates and removals. - vals.applyUpdates(updates) - vals.applyRemovals(deletes) + vals.updateValidators(updates, deletes) - if err := vals.updateTotalVotingPower(); err != nil { + if err := vals.UpdateTotalVotingPower(); err != nil { return err } @@ -575,6 +624,21 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes return nil } +func (vals *ValidatorSet) updateValidators(updates []*Validator, deletes []*Validator) { + vals.applyUpdates(updates) + vals.applyRemovals(deletes) + + vals.UpdateValidatorMap() +} + +func (vals *ValidatorSet) UpdateValidatorMap() { + vals.validatorsMap = make(map[libcommon.Address]int, len(vals.Validators)) + + for i, val := range vals.Validators { + vals.validatorsMap[val.Address] = i + } +} + // 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 @@ -597,19 +661,19 @@ func (vals *ValidatorSet) UpdateWithChangeSet(changes []*Validator) error { func IsErrTooMuchChange(err error) bool { switch err.(type) { - case errTooMuchChange: + case tooMuchChangeError: return true default: return false } } -type errTooMuchChange struct { +type tooMuchChangeError struct { got int64 needed int64 } -func (e errTooMuchChange) Error() string { +func (e tooMuchChangeError) Error() string { return fmt.Sprintf("Invalid commit -- insufficient old voting power: got %v, needed %v", e.got, e.needed) } @@ -623,11 +687,14 @@ 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: @@ -637,7 +704,6 @@ func (vals *ValidatorSet) StringIndented(indent string) string { indent, indent, strings.Join(valStrings, "\n"+indent+" "), indent) - } //------------------------------------- @@ -667,6 +733,7 @@ func safeAdd(a, b int64) (int64, bool) { } else if b < 0 && a < math.MinInt64-b { return -1, true } + return a + b, false } @@ -676,6 +743,7 @@ func safeSub(a, b int64) (int64, bool) { } else if b < 0 && a > math.MaxInt64+b { return -1, true } + return a - b, false } @@ -687,6 +755,7 @@ func safeAddClip(a, b int64) int64 { } return math.MaxInt64 } + return c } @@ -698,5 +767,6 @@ func safeSubClip(a, b int64) int64 { } return math.MaxInt64 } + return c } diff --git a/eth/backend.go b/eth/backend.go index e3707fa0824..7b7d054d7b6 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -440,7 +440,7 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere } else { consensusConfig = &config.Ethash } - backend.engine = ethconsensusconfig.CreateConsensusEngine(chainConfig, logger, consensusConfig, config.Miner.Notify, config.Miner.Noverify, config.HeimdallURL, config.WithoutHeimdall, stack.DataDir(), allSnapshots, false /* readonly */, backend.chainDB) + backend.engine = ethconsensusconfig.CreateConsensusEngine(chainConfig, logger, consensusConfig, config.Miner.Notify, config.Miner.Noverify, config.HeimdallgRPCAddress, config.HeimdallURL, config.WithoutHeimdall, stack.DataDir(), allSnapshots, false /* readonly */, backend.chainDB) backend.forkValidator = engineapi.NewForkValidator(currentBlockNumber, inMemoryExecution, tmpdir) backend.sentriesClient, err = sentry.NewMultiClient( diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index a88234ecb9e..ad02f5b83d6 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -227,6 +227,9 @@ type Config struct { // New DB and Snapshots format of history allows: parallel blocks execution, get state as of given transaction without executing whole block.", HistoryV3 bool + // gRPC Address to connect to Heimdall node + HeimdallgRPCAddress string + // URL to connect to Heimdall node HeimdallURL string diff --git a/eth/ethconsensusconfig/config.go b/eth/ethconsensusconfig/config.go index 1dd1cfbe14a..1ae814745ea 100644 --- a/eth/ethconsensusconfig/config.go +++ b/eth/ethconsensusconfig/config.go @@ -13,6 +13,10 @@ import ( "github.com/ledgerwatch/erigon/consensus/aura" "github.com/ledgerwatch/erigon/consensus/aura/consensusconfig" "github.com/ledgerwatch/erigon/consensus/bor" + "github.com/ledgerwatch/erigon/consensus/bor/contract" + "github.com/ledgerwatch/erigon/consensus/bor/heimdall" + "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" + "github.com/ledgerwatch/erigon/consensus/bor/heimdallgrpc" "github.com/ledgerwatch/erigon/consensus/clique" "github.com/ledgerwatch/erigon/consensus/db" "github.com/ledgerwatch/erigon/consensus/ethash" @@ -22,7 +26,7 @@ import ( "github.com/ledgerwatch/erigon/turbo/snapshotsync" ) -func CreateConsensusEngine(chainConfig *chain.Config, logger log.Logger, config interface{}, notify []string, noverify bool, HeimdallURL string, WithoutHeimdall bool, datadir string, snapshots *snapshotsync.RoSnapshots, readonly bool, chainDb ...kv.RwDB) consensus.Engine { +func CreateConsensusEngine(chainConfig *chain.Config, logger log.Logger, config interface{}, notify []string, noverify bool, HeimdallgRPCAddress string, HeimdallURL string, WithoutHeimdall bool, datadir string, snapshots *snapshotsync.RoSnapshots, readonly bool, chainDb ...kv.RwDB) consensus.Engine { var eng consensus.Engine switch consensusCfg := config.(type) { @@ -73,9 +77,26 @@ func CreateConsensusEngine(chainConfig *chain.Config, logger log.Logger, config eng = parlia.New(chainConfig, db.OpenDatabase(consensusCfg.DBPath, logger, consensusCfg.InMemory, readonly), snapshots, chainDb[0]) } case *chain.BorConfig: - if chainConfig.Bor != nil { + // If Matic bor consensus is requested, set it up + // In order to pass the ethereum transaction tests, we need to set the burn contract which is in the bor config + // Then, bor != nil will also be enabled for ethash and clique. Only enable Bor for real if there is a validator contract present. + if chainConfig.Bor != nil && chainConfig.Bor.ValidatorContract != "" { + genesisContractsClient := contract.NewGenesisContractsClient(chainConfig, chainConfig.Bor.ValidatorContract, chainConfig.Bor.StateReceiverContract) + spanner := span.NewChainSpanner(contract.ValidatorSet(), chainConfig) borDbPath := filepath.Join(datadir, "bor") // bor consensus path: datadir/bor - eng = bor.New(chainConfig, db.OpenDatabase(borDbPath, logger, false, readonly), HeimdallURL, WithoutHeimdall) + db := db.OpenDatabase(borDbPath, logger, false, readonly) + + var heimdallClient bor.IHeimdallClient + if WithoutHeimdall { + return bor.New(chainConfig, db, spanner, nil, genesisContractsClient) + } else { + if HeimdallgRPCAddress != "" { + heimdallClient = heimdallgrpc.NewHeimdallGRPCClient(HeimdallgRPCAddress) + } else { + heimdallClient = heimdall.NewHeimdallClient(HeimdallURL) + } + eng = bor.New(chainConfig, db, spanner, heimdallClient, genesisContractsClient) + } } } diff --git a/go.mod b/go.mod index dea319941aa..693dc74e074 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/goccy/go-json v0.9.7 github.com/gofrs/flock v0.8.1 github.com/golang-jwt/jwt/v4 v4.4.3 + github.com/golang/mock v1.6.0 github.com/golang/snappy v0.0.4 github.com/google/btree v1.1.2 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa @@ -54,6 +55,7 @@ require ( github.com/libp2p/go-libp2p v0.23.2 github.com/libp2p/go-libp2p-core v0.20.1 github.com/libp2p/go-libp2p-pubsub v0.8.1 + github.com/maticnetwork/polyproto v0.0.2 github.com/multiformats/go-multiaddr v0.7.0 github.com/nxadm/tail v1.4.9-0.20211216163028-4472660a31a6 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 diff --git a/go.sum b/go.sum index a31e9b14205..cc0dd368c12 100644 --- a/go.sum +++ b/go.sum @@ -615,6 +615,8 @@ github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbd github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/marten-seemann/webtransport-go v0.1.1 h1:TnyKp3pEXcDooTaNn4s9dYpMJ7kMnTp7k5h+SgYP/mc= +github.com/maticnetwork/polyproto v0.0.2 h1:cPxuxbIDItdwGnucc3lZB58U8Zfe1mH73PWTGd15554= +github.com/maticnetwork/polyproto v0.0.2/go.mod h1:e1mU2EXSwEpn5jM7GfNwu3AupsV6WAGoPFFfswXOF0o= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= diff --git a/tests/bor/mocks/IHeimdallClient.go b/tests/bor/mocks/IHeimdallClient.go new file mode 100644 index 00000000000..1737cae8852 --- /dev/null +++ b/tests/bor/mocks/IHeimdallClient.go @@ -0,0 +1,110 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ledgerwatch/erigon/consensus/bor (interfaces: IHeimdallClient) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + clerk "github.com/ledgerwatch/erigon/consensus/bor/clerk" + checkpoint "github.com/ledgerwatch/erigon/consensus/bor/heimdall/checkpoint" + span "github.com/ledgerwatch/erigon/consensus/bor/heimdall/span" +) + +// MockIHeimdallClient is a mock of IHeimdallClient interface. +type MockIHeimdallClient struct { + ctrl *gomock.Controller + recorder *MockIHeimdallClientMockRecorder +} + +// MockIHeimdallClientMockRecorder is the mock recorder for MockIHeimdallClient. +type MockIHeimdallClientMockRecorder struct { + mock *MockIHeimdallClient +} + +// NewMockIHeimdallClient creates a new mock instance. +func NewMockIHeimdallClient(ctrl *gomock.Controller) *MockIHeimdallClient { + mock := &MockIHeimdallClient{ctrl: ctrl} + mock.recorder = &MockIHeimdallClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIHeimdallClient) EXPECT() *MockIHeimdallClientMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockIHeimdallClient) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close. +func (mr *MockIHeimdallClientMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockIHeimdallClient)(nil).Close)) +} + +// FetchCheckpoint mocks base method. +func (m *MockIHeimdallClient) FetchCheckpoint(arg0 context.Context, arg1 int64) (*checkpoint.Checkpoint, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchCheckpoint", arg0, arg1) + ret0, _ := ret[0].(*checkpoint.Checkpoint) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchCheckpoint indicates an expected call of FetchCheckpoint. +func (mr *MockIHeimdallClientMockRecorder) FetchCheckpoint(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchCheckpoint", reflect.TypeOf((*MockIHeimdallClient)(nil).FetchCheckpoint), arg0, arg1) +} + +// FetchCheckpointCount mocks base method. +func (m *MockIHeimdallClient) FetchCheckpointCount(arg0 context.Context) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchCheckpointCount", arg0) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchCheckpointCount indicates an expected call of FetchCheckpointCount. +func (mr *MockIHeimdallClientMockRecorder) FetchCheckpointCount(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchCheckpointCount", reflect.TypeOf((*MockIHeimdallClient)(nil).FetchCheckpointCount), arg0) +} + +// Span mocks base method. +func (m *MockIHeimdallClient) Span(arg0 context.Context, arg1 uint64) (*span.HeimdallSpan, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Span", arg0, arg1) + ret0, _ := ret[0].(*span.HeimdallSpan) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Span indicates an expected call of Span. +func (mr *MockIHeimdallClientMockRecorder) Span(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Span", reflect.TypeOf((*MockIHeimdallClient)(nil).Span), arg0, arg1) +} + +// StateSyncEvents mocks base method. +func (m *MockIHeimdallClient) StateSyncEvents(arg0 context.Context, arg1 uint64, arg2 int64) ([]*clerk.EventRecordWithTime, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSyncEvents", arg0, arg1, arg2) + ret0, _ := ret[0].([]*clerk.EventRecordWithTime) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSyncEvents indicates an expected call of StateSyncEvents. +func (mr *MockIHeimdallClientMockRecorder) StateSyncEvents(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSyncEvents", reflect.TypeOf((*MockIHeimdallClient)(nil).StateSyncEvents), arg0, arg1, arg2) +} diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go index fa1a275baf6..ec49f1d0dc9 100644 --- a/turbo/cli/default_flags.go +++ b/turbo/cli/default_flags.go @@ -140,6 +140,7 @@ var DefaultFlags = []cli.Flag{ &HealthCheckFlag, &utils.HeimdallURLFlag, &utils.WithoutHeimdallFlag, + &utils.HeimdallgRPCAddressFlag, &utils.EthStatsURLFlag, &utils.OverrideShanghaiTime,