From 9c8bf51f578e4736854937eb903dd82c7d2f0432 Mon Sep 17 00:00:00 2001 From: Evgeny Danilenko <6655321@bk.ru> Date: Wed, 8 Jun 2022 16:39:30 +0300 Subject: [PATCH] Prepare Bor package for testing (#416) * Limit state sync by gas * Added logging for state-sync total gas usage * Added number of event-records in log * Minor Changes * Minor Fix * Adding individual gasUsed * Minor Fix * it works * fix tests * log wiggle and delay with block number * log delays as numbers * linters * fix tests * restore linters for the project * fix linters * fix * fix * fix * linters * generation * fix tests * remove heimdall wrapper response * linters * remove possible collisions * remove possible collisions * remove possible collisions * tests for unique address generation * generalize set * bor miner tests got restored * fixes after CR * final step and mining test * fix * fix e2e * more tests for Heimdall requests * fix linters Co-authored-by: Ferran Co-authored-by: Shivam Sharma --- .golangci.yml | 3 +- Makefile | 20 +- cmd/clidoc/main.go | 12 +- common/debug/debug.go | 28 ++ common/set/slice.go | 11 + consensus/bor/abi/interface.go | 6 + consensus/bor/api.go | 7 +- consensus/bor/api/caller.go | 14 + consensus/bor/api/caller_mock.go | 53 +++ consensus/bor/bor.go | 416 +++++-------------- consensus/bor/{ => clerk}/clerk.go | 9 +- consensus/bor/contract/client.go | 133 ++++++ consensus/bor/errors.go | 35 +- consensus/bor/genesis.go | 16 + consensus/bor/genesis_contract_mock.go | 69 +++ consensus/bor/genesis_contracts_client.go | 113 ----- consensus/bor/heimdall.go | 13 + consensus/bor/heimdall/client.go | 222 ++++++++++ consensus/bor/heimdall/client_test.go | 35 ++ consensus/bor/heimdall/span/span.go | 20 + consensus/bor/heimdall/span/spanner.go | 200 +++++++++ consensus/bor/rest.go | 177 -------- consensus/bor/snapshot.go | 22 +- consensus/bor/snapshot_test.go | 83 +++- consensus/bor/span.go | 25 +- consensus/bor/span_mock.go | 84 ++++ consensus/bor/statefull/processor.go | 93 +++++ consensus/bor/validators_getter.go | 11 + consensus/bor/validators_getter_mock.go | 51 +++ consensus/bor/valset/error.go | 32 ++ consensus/bor/{ => valset}/validator.go | 3 +- consensus/bor/{ => valset}/validator_set.go | 10 +- consensus/merger.go | 2 + core/bench_test.go | 3 +- core/blockchain.go | 9 +- core/blockchain_reader.go | 16 + core/blockchain_test.go | 2 +- core/chain_makers.go | 16 +- core/state_processor_test.go | 5 +- core/{ => tests}/blockchain_repair_test.go | 136 +++++- core/{ => tests}/blockchain_sethead_test.go | 49 ++- core/{ => tests}/blockchain_snapshot_test.go | 95 +++-- eth/backend.go | 14 +- eth/ethconfig/config.go | 8 +- eth/tracers/api_bor.go | 7 + go.mod | 79 +++- go.sum | 75 +--- internal/cli/flagset/flagset.go | 21 +- internal/cli/server/chains/chain.go | 11 +- internal/cli/server/chains/chain_test.go | 8 + internal/cli/server/command.go | 1 + internal/cli/server/config.go | 53 ++- internal/cli/server/config_test.go | 13 + internal/cli/server/helper.go | 17 +- internal/cli/server/server.go | 39 +- internal/cli/server/server_test.go | 6 + internal/cli/server/service.go | 21 +- miner/fake_miner.go | 233 +++++++++++ miner/miner_test.go | 212 +++++----- miner/unconfirmed_test.go | 7 + miner/worker.go | 4 + miner/worker_test.go | 163 ++++++-- params/config.go | 38 +- tests/bor/bor_test.go | 107 +++-- tests/bor/helper.go | 57 ++- tests/bor/mocks/IHeimdallClient.go | 135 +++--- tests/deps/fake.go | 7 + trie/hasher.go | 3 +- 68 files changed, 2517 insertions(+), 1181 deletions(-) create mode 100644 common/debug/debug.go create mode 100644 common/set/slice.go create mode 100644 consensus/bor/abi/interface.go create mode 100644 consensus/bor/api/caller.go create mode 100644 consensus/bor/api/caller_mock.go rename consensus/bor/{ => clerk}/clerk.go (85%) create mode 100644 consensus/bor/contract/client.go create mode 100644 consensus/bor/genesis.go create mode 100644 consensus/bor/genesis_contract_mock.go delete mode 100644 consensus/bor/genesis_contracts_client.go create mode 100644 consensus/bor/heimdall.go create mode 100644 consensus/bor/heimdall/client.go create mode 100644 consensus/bor/heimdall/client_test.go create mode 100644 consensus/bor/heimdall/span/span.go create mode 100644 consensus/bor/heimdall/span/spanner.go delete mode 100644 consensus/bor/rest.go create mode 100644 consensus/bor/span_mock.go create mode 100644 consensus/bor/statefull/processor.go create mode 100644 consensus/bor/validators_getter.go create mode 100644 consensus/bor/validators_getter_mock.go create mode 100644 consensus/bor/valset/error.go rename consensus/bor/{ => valset}/validator.go (99%) rename consensus/bor/{ => valset}/validator_set.go (99%) rename core/{ => tests}/blockchain_repair_test.go (93%) rename core/{ => tests}/blockchain_sethead_test.go (98%) rename core/{ => tests}/blockchain_snapshot_test.go (87%) create mode 100644 miner/fake_miner.go create mode 100644 tests/deps/fake.go diff --git a/.golangci.yml b/.golangci.yml index b063984061..89a9e328b8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,7 @@ # This file configures github.com/golangci/golangci-lint. run: + go: '1.18' timeout: 20m tests: true # default is true. Enables skipping of directories: @@ -183,4 +184,4 @@ issues: max-issues-per-linter: 0 max-same-issues: 0 #new: true - # new-from-rev: origin/master \ No newline at end of file + new-from-rev: origin/master \ No newline at end of file diff --git a/Makefile b/Makefile index 53b3cd9fdb..46ecdec886 100644 --- a/Makefile +++ b/Makefile @@ -65,9 +65,7 @@ escape: cd $(path) && go test -gcflags "-m -m" -run none -bench=BenchmarkJumpdest* -benchmem -memprofile mem.out lint: - @./build/bin/golangci-lint run --config ./.golangci.yml \ - internal/cli \ - consensus/bor + @./build/bin/golangci-lint run --config ./.golangci.yml lintci-deps: rm -f ./build/bin/golangci-lint @@ -87,16 +85,20 @@ clean: # You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'. devtools: - env GOBIN= go install golang.org/x/tools/cmd/stringer@latest - env GOBIN= go install github.com/kevinburke/go-bindata/go-bindata@latest - env GOBIN= go install github.com/fjl/gencodec@latest - env GOBIN= go install github.com/golang/protobuf/protoc-gen-go@latest - env GOBIN= go install ./cmd/abigen + # Notice! If you adding new binary - add it also to tests/deps/fake.go file + $(GOBUILD) -o $(GOBIN)/stringer github.com/golang.org/x/tools/cmd/stringer + $(GOBUILD) -o $(GOBIN)/go-bindata github.com/kevinburke/go-bindata/go-bindata + $(GOBUILD) -o $(GOBIN)/codecgen github.com/ugorji/go/codec/codecgen + $(GOBUILD) -o $(GOBIN)/abigen ./cmd/abigen + $(GOBUILD) -o $(GOBIN)/mockgen github.com/golang/mock/mockgen + $(GOBUILD) -o $(GOBIN)/protoc-gen-go github.com/golang/protobuf/protoc-gen-go + PATH=$(GOBIN):$(PATH) go generate ./common + PATH=$(GOBIN):$(PATH) go generate ./core/types + PATH=$(GOBIN):$(PATH) go generate ./consensus/bor @type "solc" 2> /dev/null || echo 'Please install solc' @type "protoc" 2> /dev/null || echo 'Please install protoc' # Cross Compilation Targets (xgo) - geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios @echo "Full cross compilation done:" @ls -ld $(GOBIN)/geth-* diff --git a/cmd/clidoc/main.go b/cmd/clidoc/main.go index 70ad35501b..07efc04556 100644 --- a/cmd/clidoc/main.go +++ b/cmd/clidoc/main.go @@ -17,7 +17,6 @@ const ( ) func main() { - commands := cli.Commands() dest := flag.String("d", DefaultDir, "Destination directory where the docs will be generated") @@ -35,10 +34,12 @@ func main() { keys := make([]string, len(commands)) i := 0 + for k := range commands { keys[i] = k i++ } + sort.Strings(keys) for _, name := range keys { @@ -60,12 +61,17 @@ func main() { func overwriteFile(filePath string, text string) { log.Printf("Writing to page: %s\n", filePath) + f, err := os.Create(filePath) if err != nil { log.Fatalln(err) } - f.WriteString(text) - if err := f.Close(); err != nil { + + if _, err = f.WriteString(text); err != nil { + log.Fatalln(err) + } + + if err = f.Close(); err != nil { log.Fatalln(err) } } diff --git a/common/debug/debug.go b/common/debug/debug.go new file mode 100644 index 0000000000..6a677e495d --- /dev/null +++ b/common/debug/debug.go @@ -0,0 +1,28 @@ +package debug + +import ( + "runtime" +) + +// Callers returns given number of callers with packages +func Callers(show int) []string { + fpcs := make([]uintptr, show) + + n := runtime.Callers(2, fpcs) + if n == 0 { + return nil + } + + callers := make([]string, 0, len(fpcs)) + + for _, p := range fpcs { + caller := runtime.FuncForPC(p - 1) + if caller == nil { + continue + } + + callers = append(callers, caller.Name()) + } + + return callers +} diff --git a/common/set/slice.go b/common/set/slice.go new file mode 100644 index 0000000000..36f11e67fe --- /dev/null +++ b/common/set/slice.go @@ -0,0 +1,11 @@ +package set + +func New[T comparable](slice []T) map[T]struct{} { + m := make(map[T]struct{}, len(slice)) + + for _, el := range slice { + m[el] = struct{}{} + } + + return m +} diff --git a/consensus/bor/abi/interface.go b/consensus/bor/abi/interface.go new file mode 100644 index 0000000000..bb05bf0b23 --- /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 364fc448b8..26d1efdaf1 100644 --- a/consensus/bor/api.go +++ b/consensus/bor/api.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rpc" @@ -206,10 +207,10 @@ func (api *API) GetCurrentProposer() (common.Address, error) { } // 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 @@ -236,7 +237,7 @@ func (api *API) GetRootHash(start uint64, end uint64) (string, error) { 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) diff --git a/consensus/bor/api/caller.go b/consensus/bor/api/caller.go new file mode 100644 index 0000000000..d5fe259c97 --- /dev/null +++ b/consensus/bor/api/caller.go @@ -0,0 +1,14 @@ +package api + +import ( + "context" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/rpc" +) + +//go:generate mockgen -destination=./caller_mock.go -package=api . Caller +type Caller interface { + Call(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi.StateOverride) (hexutil.Bytes, error) +} diff --git a/consensus/bor/api/caller_mock.go b/consensus/bor/api/caller_mock.go new file mode 100644 index 0000000000..940c99d178 --- /dev/null +++ b/consensus/bor/api/caller_mock.go @@ -0,0 +1,53 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ethereum/go-ethereum/consensus/bor/api (interfaces: Caller) + +// Package api is a generated GoMock package. +package api + +import ( + context "context" + reflect "reflect" + + hexutil "github.com/ethereum/go-ethereum/common/hexutil" + ethapi "github.com/ethereum/go-ethereum/internal/ethapi" + rpc "github.com/ethereum/go-ethereum/rpc" + gomock "github.com/golang/mock/gomock" +) + +// MockCaller is a mock of Caller interface. +type MockCaller struct { + ctrl *gomock.Controller + recorder *MockCallerMockRecorder +} + +// MockCallerMockRecorder is the mock recorder for MockCaller. +type MockCallerMockRecorder struct { + mock *MockCaller +} + +// NewMockCaller creates a new mock instance. +func NewMockCaller(ctrl *gomock.Controller) *MockCaller { + mock := &MockCaller{ctrl: ctrl} + mock.recorder = &MockCallerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCaller) EXPECT() *MockCallerMockRecorder { + return m.recorder +} + +// Call mocks base method. +func (m *MockCaller) Call(arg0 context.Context, arg1 ethapi.TransactionArgs, arg2 rpc.BlockNumberOrHash, arg3 *ethapi.StateOverride) (hexutil.Bytes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Call", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(hexutil.Bytes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Call indicates an expected call of Call. +func (mr *MockCallerMockRecorder) Call(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockCaller)(nil).Call), arg0, arg1, arg2, arg3) +} diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 9e43e67037..2f087af787 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -2,37 +2,34 @@ package bor import ( "bytes" - "context" "encoding/hex" "encoding/json" "errors" "fmt" "io" - "math" "math/big" "sort" "strconv" - "strings" "sync" "time" lru "github.com/hashicorp/golang-lru" "golang.org/x/crypto/sha3" - ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/bor/api" + "github.com/ethereum/go-ethereum/consensus/bor/clerk" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" + "github.com/ethereum/go-ethereum/consensus/bor/statefull" + "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -56,7 +53,6 @@ var ( uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. validatorHeaderBytesLength = common.AddressLength + 20 // address + power - systemAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") ) // Various error messages to mark blocks invalid. These should be private to @@ -100,9 +96,6 @@ 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") - - // errShutdownDetected is returned if a shutdown was detected - errShutdownDetected = errors.New("shutdown detected") ) // SignerFn is a signer callback function to request a header to be signed by a @@ -220,24 +213,25 @@ type Bor struct { signFn SignerFn // Signer function to authorize hashes with lock sync.RWMutex // Protects the signer fields - ethAPI *ethapi.PublicBlockChainAPI - GenesisContractsClient *GenesisContractsClient - validatorSetABI abi.ABI - stateReceiverABI abi.ABI + ethAPI api.Caller + spanner Spanner + GenesisContractsClient GenesisContract HeimdallClient IHeimdallClient - WithoutHeimdall bool // The fields below are for testing only fakeDiff bool // Skip difficulty verifications + + closeOnce sync.Once } // New creates a Matic Bor consensus engine. func New( chainConfig *params.ChainConfig, db ethdb.Database, - ethAPI *ethapi.PublicBlockChainAPI, - heimdallURL string, - withoutHeimdall bool, + ethAPI api.Caller, + spanner Spanner, + heimdallClient IHeimdallClient, + genesisContracts GenesisContract, ) *Bor { // get bor config borConfig := chainConfig.Bor @@ -246,14 +240,10 @@ func New( if borConfig != nil && borConfig.Sprint == 0 { borConfig.Sprint = defaultSprintLength } - // Allocate the snapshot caches and create the engine recents, _ := lru.NewARC(inmemorySnapshots) signatures, _ := lru.NewARC(inmemorySignatures) - vABI, _ := abi.JSON(strings.NewReader(validatorsetABI)) - sABI, _ := abi.JSON(strings.NewReader(stateReceiverABI)) - heimdallClient, _ := NewHeimdallClient(heimdallURL) - genesisContractsClient := NewGenesisContractsClient(chainConfig, borConfig.ValidatorContract, borConfig.StateReceiverContract, ethAPI) + c := &Bor{ chainConfig: chainConfig, config: borConfig, @@ -261,11 +251,9 @@ func New( ethAPI: ethAPI, recents: recents, signatures: signatures, - validatorSetABI: vABI, - stateReceiverABI: sABI, - GenesisContractsClient: genesisContractsClient, + spanner: spanner, + GenesisContractsClient: genesisContracts, HeimdallClient: heimdallClient, - WithoutHeimdall: withoutHeimdall, } // make sure we can decode all the GenesisAlloc in the BorConfig. @@ -332,7 +320,7 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head } // check extr adata - isSprintEnd := (number+1)%c.config.Sprint == 0 + isSprintEnd := IsSprintStart(number+1, c.config.Sprint) // Ensure that the extra-data contains a signer list on checkpoint, but none otherwise signersBytes := len(header.Extra) - extraVanity - extraSeal @@ -441,19 +429,18 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainHeaderReader, header *t // Retrieve the snapshot needed to verify this header and cache it snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) - if err != nil { return err } // verify the validator list in the last sprint block - if isSprintStart(number, c.config.Sprint) { + if IsSprintStart(number, c.config.Sprint) { parentValidatorBytes := parent.Extra[extraVanity : len(parent.Extra)-extraSeal] validatorsBytes := make([]byte, len(snap.ValidatorSet.Validators)*validatorHeaderBytesLength) currentValidators := snap.ValidatorSet.Copy().Validators // sort validator by address - sort.Sort(ValidatorsByAddress(currentValidators)) + sort.Sort(valset.ValidatorsByAddress(currentValidators)) for i, validator := range currentValidators { copy(validatorsBytes[i*validatorHeaderBytesLength:], validator.HeaderBytes()) @@ -487,7 +474,7 @@ func (c *Bor) snapshot(chain consensus.ChainHeaderReader, number uint64, hash co // If an on-disk checkpoint snapshot can be found, use that if number%checkpointInterval == 0 { - if s, err := loadSnapshot(c.config, c.signatures, c.db, hash, c.ethAPI); err == nil { + 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 @@ -509,13 +496,13 @@ func (c *Bor) snapshot(chain consensus.ChainHeaderReader, number uint64, hash co hash := checkpoint.Hash() // get validators and current span - validators, err := c.GetCurrentValidators(hash, number+1) + validators, err := c.spanner.GetCurrentValidators(hash, number+1) if err != nil { return nil, err } // new snap shot - snap = newSnapshot(c.config, c.signatures, number, hash, validators, c.ethAPI) + snap = newSnapshot(c.config, c.signatures, number, hash, validators) if err := snap.store(c.db); err != nil { return nil, err } @@ -674,14 +661,14 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header) e header.Extra = header.Extra[:extraVanity] // get validator set if number - if (number+1)%c.config.Sprint == 0 { - newValidators, err := c.GetCurrentValidators(header.ParentHash, number+1) + if IsSprintStart(number+1, c.config.Sprint) { + newValidators, err := c.spanner.GetCurrentValidators(header.ParentHash, number+1) if err != nil { return errors.New("unknown validators") } // sort validator by address - sort.Sort(ValidatorsByAddress(newValidators)) + sort.Sort(valset.ValidatorsByAddress(newValidators)) for _, validator := range newValidators { header.Extra = append(header.Extra, validator.HeaderBytes()...) @@ -727,14 +714,14 @@ func (c *Bor) Finalize(chain consensus.ChainHeaderReader, header *types.Header, headerNumber := header.Number.Uint64() if headerNumber%c.config.Sprint == 0 { - cx := chainContext{Chain: chain, Bor: c} + cx := statefull.ChainContext{Chain: chain, Bor: c} // check and commit span if err := c.checkAndCommitSpan(state, header, cx); err != nil { log.Error("Error while committing span", "error", err) return } - if !c.WithoutHeimdall { + if c.HeimdallClient != nil { // commit statees stateSyncData, err = c.CommitStates(state, header, cx) if err != nil { @@ -762,7 +749,6 @@ func decodeGenesisAlloc(i interface{}) (core.GenesisAlloc, error) { var alloc core.GenesisAlloc b, err := json.Marshal(i) - if err != nil { return nil, err } @@ -800,7 +786,7 @@ func (c *Bor) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *typ headerNumber := header.Number.Uint64() if headerNumber%c.config.Sprint == 0 { - cx := chainContext{Chain: chain, Bor: c} + cx := statefull.ChainContext{Chain: chain, Bor: c} // check and commit span err := c.checkAndCommitSpan(state, header, cx) @@ -809,7 +795,7 @@ func (c *Bor) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *typ return nil, err } - if !c.WithoutHeimdall { + if c.HeimdallClient != nil { // commit states stateSyncData, err = c.CommitStates(state, header, cx) if err != nil { @@ -832,7 +818,7 @@ func (c *Bor) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *typ block := types.NewBlock(header, txs, nil, receipts, new(trie.Trie)) // set state sync - bc := chain.(*core.BlockChain) + bc := chain.(core.BorStateSyncer) bc.SetStateSync(stateSyncData) // return the final block for sealing @@ -890,15 +876,13 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result wiggle := time.Duration(successionNumber) * time.Duration(c.config.CalculateBackupMultiplier(number)) * time.Second // Sign all the things! - sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeBor, BorRLP(header, c.config)) + err = Sign(signFn, signer, header, c.config) if err != nil { return err } - copy(header.Extra[len(header.Extra)-extraSeal:], sighash) - // Wait until sealing is terminated or delay timeout. - log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) + 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 { @@ -910,6 +894,8 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result log.Info( "Sealing out-of-turn", "number", number, + "hash", header.Hash, + "wiggle-in-sec", uint(wiggle), "wiggle", common.PrettyDuration(wiggle), "in-turn-signer", snap.ValidatorSet.GetProposer().Address.Hex(), ) @@ -932,6 +918,17 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result return nil } +func Sign(signFn SignerFn, signer common.Address, header *types.Header, c *params.BorConfig) error { + sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeBor, BorRLP(header, c)) + if err != nil { + return err + } + + copy(header.Extra[len(header.Extra)-extraSeal:], sighash) + + return nil +} + // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty // that a new block should have based on the previous blocks in the chain and the // current signer. @@ -962,114 +959,11 @@ 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.HeimdallClient.Close() - return nil -} - -// GetCurrentSpan get current span from contract -func (c *Bor) GetCurrentSpan(headerHash common.Hash) (*Span, error) { - // block - blockNr := rpc.BlockNumberOrHashWithHash(headerHash, false) - - // method - method := "getCurrentSpan" - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - data, err := c.validatorSetABI.Pack(method) - if err != nil { - log.Error("Unable to pack tx for getCurrentSpan", "error", err) - return nil, err - } - - msgData := (hexutil.Bytes)(data) - toAddress := common.HexToAddress(c.config.ValidatorContract) - gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) - result, err := c.ethAPI.Call(ctx, ethapi.TransactionArgs{ - Gas: &gas, - To: &toAddress, - Data: &msgData, - }, blockNr, nil) - - if err != nil { - return nil, err - } - - // span result - ret := new(struct { - Number *big.Int - StartBlock *big.Int - EndBlock *big.Int + c.closeOnce.Do(func() { + c.HeimdallClient.Close() }) - 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(headerHash common.Hash, blockNumber uint64) ([]*Validator, error) { - // block - blockNr := rpc.BlockNumberOrHashWithHash(headerHash, false) - - // method - method := "getBorValidators" - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - data, err := c.validatorSetABI.Pack(method, big.NewInt(0).SetUint64(blockNumber)) - if err != nil { - log.Error("Unable to pack tx for getValidator", "error", err) - return nil, err - } - - // call - msgData := (hexutil.Bytes)(data) - toAddress := common.HexToAddress(c.config.ValidatorContract) - gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) - result, err := c.ethAPI.Call(ctx, ethapi.TransactionArgs{ - Gas: &gas, - To: &toAddress, - Data: &msgData, - }, blockNr, nil) - - if err != nil { - panic(err) - } - - var ( - ret0 = new([]common.Address) - ret1 = new([]*big.Int) - ) - - out := &[]interface{}{ - ret0, - ret1, - } - - if err := c.validatorSetABI.UnpackIntoInterface(out, method, result); err != nil { - return nil, err - } - - valz := make([]*Validator, len(*ret0)) - for i, a := range *ret0 { - valz[i] = &Validator{ - Address: a, - VotingPower: (*ret1)[i].Int64(), - } - } - - return valz, nil + return nil } func (c *Bor) checkAndCommitSpan( @@ -1078,21 +972,20 @@ func (c *Bor) checkAndCommitSpan( chain core.ChainContext, ) error { headerNumber := header.Number.Uint64() - span, err := c.GetCurrentSpan(header.ParentHash) + span, err := c.spanner.GetCurrentSpan(header.ParentHash) if err != nil { return err } if c.needToCommitSpan(span, headerNumber) { - err := c.fetchAndCommitSpan(span.ID+1, state, header, chain) - return err + return c.FetchAndCommitSpan(span.ID+1, state, header, chain) } return nil } -func (c *Bor) needToCommitSpan(span *Span, headerNumber uint64) bool { +func (c *Bor) needToCommitSpan(span *span.Span, headerNumber uint64) bool { // if span is nil if span == nil { return false @@ -1111,15 +1004,16 @@ func (c *Bor) needToCommitSpan(span *Span, headerNumber uint64) bool { return false } -func (c *Bor) fetchAndCommitSpan( +func (c *Bor) FetchAndCommitSpan( newSpanID uint64, state *state.StateDB, header *types.Header, chain core.ChainContext, ) 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, header, chain) if err != nil { return err @@ -1127,105 +1021,51 @@ func (c *Bor) fetchAndCommitSpan( heimdallSpan = *s } else { - response, err := c.HeimdallClient.FetchWithRetry(fmt.Sprintf("bor/span/%d", newSpanID), "") + response, err := c.HeimdallClient.Span(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 + // check if chain id matches with Heimdall span if heimdallSpan.ChainID != c.chainConfig.ChainID.String() { return fmt.Errorf( - "Chain id proposed span, %s, and bor chain id, %s, doesn't match", + "chain id proposed span, %s, and bor chain id, %s, doesn't match", heimdallSpan.ChainID, c.chainConfig.ChainID, ) } - // 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.Info("✅ Committing new span", - "id", heimdallSpan.ID, - "startBlock", heimdallSpan.StartBlock, - "endBlock", heimdallSpan.EndBlock, - "validatorBytes", hex.EncodeToString(validatorBytes), - "producerBytes", hex.EncodeToString(producerBytes), - ) - - // get packed data - data, err := c.validatorSetABI.Pack(method, - big.NewInt(0).SetUint64(heimdallSpan.ID), - big.NewInt(0).SetUint64(heimdallSpan.StartBlock), - big.NewInt(0).SetUint64(heimdallSpan.EndBlock), - validatorBytes, - producerBytes, - ) - if err != nil { - log.Error("Unable to pack tx for commitSpan", "error", err) - return err - } - - // get system message - msg := getSystemMessage(common.HexToAddress(c.config.ValidatorContract), data) - - // apply message - return applyMessage(msg, state, header, c.chainConfig, chain) + return c.spanner.CommitSpan(heimdallSpan, state, header, chain) } // CommitStates commit states func (c *Bor) CommitStates( state *state.StateDB, header *types.Header, - chain chainContext, + chain statefull.ChainContext, ) ([]*types.StateSyncData, error) { stateSyncs := make([]*types.StateSyncData, 0) number := header.Number.Uint64() - _lastStateID, err := c.GenesisContractsClient.LastStateId(number - 1) + _lastStateID, err := c.GenesisContractsClient.LastStateId(number - 1) if err != nil { return nil, err } to := time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.Sprint).Time), 0) lastStateID := _lastStateID.Uint64() + log.Info( "Fetching state updates from Heimdall", "fromID", lastStateID+1, "to", to.Format(time.RFC3339)) - eventRecords, err := c.HeimdallClient.FetchStateSyncEvents(lastStateID+1, to.Unix()) - + eventRecords, err := c.HeimdallClient.StateSyncEvents(lastStateID+1, to.Unix()) if err != nil { - log.Error("Error occurred when fetching state sync events", err) + log.Error("Error occurred when fetching state sync events", "stateID", lastStateID+1, "error", err) } if c.config.OverrideStateSyncRecords != nil { @@ -1234,6 +1074,8 @@ func (c *Bor) CommitStates( } } + totalGas := 0 /// limit on gas for state sync per block + chainID := c.chainConfig.ChainID.String() for _, eventRecord := range eventRecords { @@ -1242,7 +1084,7 @@ func (c *Bor) CommitStates( } if err := validateEventRecord(eventRecord, number, to, lastStateID, chainID); err != nil { - log.Error(err.Error()) + log.Error("while validating event record", "block", number, "to", to, "stateID", lastStateID, "error", err.Error()) break } @@ -1252,18 +1094,23 @@ func (c *Bor) CommitStates( Data: hex.EncodeToString(eventRecord.Data), TxHash: eventRecord.TxHash, } + stateSyncs = append(stateSyncs, &stateData) - if err := c.GenesisContractsClient.CommitState(eventRecord, state, header, chain); err != nil { + gasUsed, err := c.GenesisContractsClient.CommitState(eventRecord, state, header, chain) + if err != nil { return nil, err } + + totalGas += int(gasUsed) + 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} @@ -1276,6 +1123,10 @@ func (c *Bor) SetHeimdallClient(h IHeimdallClient) { c.HeimdallClient = h } +func (c *Bor) GetCurrentValidators(headerHash common.Hash, blockNumber uint64) ([]*valset.Validator, error) { + return c.spanner.GetCurrentValidators(headerHash, blockNumber) +} + // // Private methods // @@ -1284,16 +1135,16 @@ func (c *Bor) getNextHeimdallSpanForTest( newSpanID uint64, header *types.Header, chain core.ChainContext, -) (*HeimdallSpan, error) { +) (*span.HeimdallSpan, error) { headerNumber := header.Number.Uint64() - span, err := c.GetCurrentSpan(header.ParentHash) + spanBor, err := c.spanner.GetCurrentSpan(header.ParentHash) if err != nil { return nil, err } // get local chain context object - localContext := chain.(chainContext) + localContext := chain.(statefull.ChainContext) // Retrieve the snapshot needed to verify this header and cache it snap, err := c.snapshot(localContext.Chain, headerNumber-1, header.ParentHash, nil) if err != nil { @@ -1301,22 +1152,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.Sprint) - 1 + spanBor.EndBlock = spanBor.StartBlock + (100 * c.config.Sprint) - 1 - selectedProducers := make([]Validator, len(snap.ValidatorSet.Validators)) + 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(), @@ -1325,82 +1176,7 @@ 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 common.Hash, number uint64) *types.Header { - return c.Chain.GetHeader(hash, number) -} - -// callmsg implements core.Message to allow passing it as a transaction simulator. -type callmsg struct { - ethereum.CallMsg -} - -func (m callmsg) From() common.Address { return m.CallMsg.From } -func (m callmsg) Nonce() uint64 { return 0 } -func (m callmsg) CheckNonce() bool { return false } -func (m callmsg) To() *common.Address { return m.CallMsg.To } -func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } -func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } -func (m callmsg) Value() *big.Int { return m.CallMsg.Value } -func (m callmsg) Data() []byte { return m.CallMsg.Data } - -// get system message -func getSystemMessage(toAddress common.Address, data []byte) callmsg { - return callmsg{ - ethereum.CallMsg{ - From: systemAddress, - Gas: math.MaxUint64 / 2, - GasPrice: big.NewInt(0), - Value: big.NewInt(0), - To: &toAddress, - Data: data, - }, - } -} - -// apply message -func applyMessage( - msg callmsg, - state *state.StateDB, - header *types.Header, - chainConfig *params.ChainConfig, - chainContext core.ChainContext, -) error { - // Create a new context to be used in the EVM environment - blockContext := core.NewEVMBlockContext(header, chainContext, &header.Coinbase) - // Create a new environment which holds all relevant information - // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(blockContext, vm.TxContext{}, state, chainConfig, vm.Config{}) - // Apply the transaction to the current state (included in the env) - _, _, err := vmenv.Call( - vm.AccountRef(msg.From()), - *msg.To(), - msg.Data(), - msg.Gas(), - msg.Value(), - ) - // Update the state with pending changes - if err != nil { - state.Finalise(true) - } - - return nil -} - -func validatorContains(a []*Validator, x *Validator) (*Validator, bool) { +func validatorContains(a []*valset.Validator, x *valset.Validator) (*valset.Validator, bool) { for _, n := range a { if n.Address == x.Address { return n, true @@ -1410,11 +1186,11 @@ func validatorContains(a []*Validator, x *Validator) (*Validator, bool) { 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 { @@ -1433,12 +1209,12 @@ func getUpdatedValidatorSet(oldValidatorSet *ValidatorSet, newVals []*Validator) } if err := v.UpdateWithChangeSet(changes); err != nil { - log.Error("Error while updating change set", err) + log.Error("Error while updating change set", "error", err) } return v } -func isSprintStart(number, sprint uint64) bool { +func IsSprintStart(number, sprint uint64) bool { return number%sprint == 0 } diff --git a/consensus/bor/clerk.go b/consensus/bor/clerk/clerk.go similarity index 85% rename from consensus/bor/clerk.go rename to consensus/bor/clerk/clerk.go index ca610282a5..fedca3df16 100644 --- a/consensus/bor/clerk.go +++ b/consensus/bor/clerk/clerk.go @@ -1,4 +1,4 @@ -package bor +package clerk import ( "fmt" @@ -23,10 +23,10 @@ type EventRecordWithTime struct { Time time.Time `json:"record_time" yaml:"record_time"` } -// String returns the string representations of span -func (e *EventRecordWithTime) String() string { +// String returns the string representation of EventRecord +func (e *EventRecordWithTime) String(gasUsed uint64) string { return fmt.Sprintf( - "id %v, contract %v, data: %v, txHash: %v, logIndex: %v, chainId: %v, time %s", + "id %v, contract %v, data: %v, txHash: %v, logIndex: %v, chainId: %v, time %s, gasUsed %d", e.ID, e.Contract.String(), e.Data.String(), @@ -34,6 +34,7 @@ func (e *EventRecordWithTime) String() string { e.LogIndex, e.ChainID, e.Time.Format(time.RFC3339), + gasUsed, ) } diff --git a/consensus/bor/contract/client.go b/consensus/bor/contract/client.go new file mode 100644 index 0000000000..d65fb5bb15 --- /dev/null +++ b/consensus/bor/contract/client.go @@ -0,0 +1,133 @@ +package contract + +import ( + "context" + "math" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/bor/api" + "github.com/ethereum/go-ethereum/consensus/bor/clerk" + "github.com/ethereum/go-ethereum/consensus/bor/statefull" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" +) + +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 *params.ChainConfig + ethAPI api.Caller +} + +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 *params.ChainConfig, + validatorContract, + stateReceiverContract string, + ethAPI api.Caller, +) *GenesisContractsClient { + return &GenesisContractsClient{ + validatorSetABI: ValidatorSet(), + stateReceiverABI: StateReceiver(), + ValidatorContract: validatorContract, + StateReceiverContract: stateReceiverContract, + chainConfig: chainConfig, + ethAPI: ethAPI, + } +} + +func (gc *GenesisContractsClient) CommitState( + event *clerk.EventRecordWithTime, + state *state.StateDB, + header *types.Header, + chCtx statefull.ChainContext, +) (uint64, error) { + eventRecord := event.BuildEventRecord() + + recordBytes, err := rlp.EncodeToBytes(eventRecord) + if err != nil { + return 0, 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", "error", err) + return 0, err + } + + msg := statefull.GetSystemMessage(common.HexToAddress(gc.StateReceiverContract), data) + gasUsed, err := statefull.ApplyMessage(msg, state, header, gc.chainConfig, chCtx) + + // Logging event log with time and individual gasUsed + log.Info("→ committing new state", "eventRecord", event.String(gasUsed)) + + if err != nil { + return 0, err + } + + return gasUsed, nil +} + +func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int, error) { + blockNr := rpc.BlockNumber(snapshotNumber) + method := "lastStateId" + + data, err := gc.stateReceiverABI.Pack(method) + if err != nil { + log.Error("Unable to pack tx for LastStateId", "error", err) + + return nil, err + } + + msgData := (hexutil.Bytes)(data) + toAddress := common.HexToAddress(gc.StateReceiverContract) + gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) + + result, err := gc.ethAPI.Call(context.Background(), ethapi.TransactionArgs{ + Gas: &gas, + To: &toAddress, + Data: &msgData, + }, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil) + if err != nil { + return nil, err + } + + 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 a1e60d1e21..67f7ef53f3 100644 --- a/consensus/bor/errors.go +++ b/consensus/bor/errors.go @@ -3,37 +3,10 @@ package bor import ( "fmt" "time" + + "github.com/ethereum/go-ethereum/consensus/bor/clerk" ) -// TotalVotingPowerExceededError is returned when the maximum allowed total voting power is exceeded -type TotalVotingPowerExceededError struct { - Sum int64 - Validators []*Validator -} - -func (e *TotalVotingPowerExceededError) Error() string { - return fmt.Sprintf( - "Total voting power should be guarded to not exceed %v; got: %v; for validator set: %v", - MaxTotalVotingPower, - e.Sum, - e.Validators, - ) -} - -type InvalidStartEndBlockError struct { - Start uint64 - End uint64 - CurrentHeader uint64 -} - -func (e *InvalidStartEndBlockError) Error() string { - return fmt.Sprintf( - "Invalid parameters start: %d and end block: %d params", - e.Start, - e.End, - ) -} - type MaxCheckpointLengthExceededError struct { Start uint64 End uint64 @@ -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 0000000000..33de53f9ba --- /dev/null +++ b/consensus/bor/genesis.go @@ -0,0 +1,16 @@ +package bor + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/consensus/bor/clerk" + "github.com/ethereum/go-ethereum/consensus/bor/statefull" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" +) + +//go:generate mockgen -destination=./genesis_contract_mock.go -package=bor . GenesisContract +type GenesisContract interface { + CommitState(event *clerk.EventRecordWithTime, state *state.StateDB, header *types.Header, chCtx statefull.ChainContext) (uint64, error) + LastStateId(snapshotNumber uint64) (*big.Int, error) +} diff --git a/consensus/bor/genesis_contract_mock.go b/consensus/bor/genesis_contract_mock.go new file mode 100644 index 0000000000..dfe9390509 --- /dev/null +++ b/consensus/bor/genesis_contract_mock.go @@ -0,0 +1,69 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: GenesisContract) + +// Package bor is a generated GoMock package. +package bor + +import ( + big "math/big" + reflect "reflect" + + clerk "github.com/ethereum/go-ethereum/consensus/bor/clerk" + statefull "github.com/ethereum/go-ethereum/consensus/bor/statefull" + state "github.com/ethereum/go-ethereum/core/state" + types "github.com/ethereum/go-ethereum/core/types" + gomock "github.com/golang/mock/gomock" +) + +// 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 *state.StateDB, arg2 *types.Header, arg3 statefull.ChainContext) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitState", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CommitState indicates an expected call of CommitState. +func (mr *MockGenesisContractMockRecorder) CommitState(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitState", reflect.TypeOf((*MockGenesisContract)(nil).CommitState), arg0, arg1, arg2, arg3) +} + +// LastStateId mocks base method. +func (m *MockGenesisContract) LastStateId(arg0 uint64) (*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 2b36b6e5e0..0000000000 --- a/consensus/bor/genesis_contracts_client.go +++ /dev/null @@ -1,113 +0,0 @@ -package bor - -import ( - "context" - "math" - "math/big" - "strings" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" -) - -type GenesisContractsClient struct { - validatorSetABI abi.ABI - stateReceiverABI abi.ABI - ValidatorContract string - StateReceiverContract string - chainConfig *params.ChainConfig - ethAPI *ethapi.PublicBlockChainAPI -} - -const validatorsetABI = `[{"constant":true,"inputs":[],"name":"SPRINT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SYSTEM_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"CHAIN","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"FIRST_END_BLOCK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"producers","outputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"ROUND_TYPE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BOR_ID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"spanNumbers","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"VOTE_TYPE","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"validators","outputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"spans","outputs":[{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"startBlock","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"endBlock","type":"uint256"}],"name":"NewSpan","type":"event"},{"constant":true,"inputs":[],"name":"currentSprint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"}],"name":"getSpan","outputs":[{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentSpan","outputs":[{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNextSpan","outputs":[{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"number","type":"uint256"}],"name":"getSpanByBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"currentSpanNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"}],"name":"getValidatorsTotalStakeBySpan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"}],"name":"getProducersTotalStakeBySpan","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"name":"getValidatorBySigner","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"power","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"internalType":"struct BorValidatorSet.Validator","name":"result","type":"tuple"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"name":"isValidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"},{"internalType":"address","name":"signer","type":"address"}],"name":"isProducer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"isCurrentValidator","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"isCurrentProducer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"number","type":"uint256"}],"name":"getBorValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitialValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"newSpan","type":"uint256"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"bytes","name":"validatorBytes","type":"bytes"},{"internalType":"bytes","name":"producerBytes","type":"bytes"}],"name":"commitSpan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"span","type":"uint256"},{"internalType":"bytes32","name":"dataHash","type":"bytes32"},{"internalType":"bytes","name":"sigs","type":"bytes"}],"name":"getStakePowerBySigs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"rootHash","type":"bytes32"},{"internalType":"bytes32","name":"leaf","type":"bytes32"},{"internalType":"bytes","name":"proof","type":"bytes"}],"name":"checkMembership","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"d","type":"bytes32"}],"name":"leafNode","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"left","type":"bytes32"},{"internalType":"bytes32","name":"right","type":"bytes32"}],"name":"innerNode","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"pure","type":"function"}]` -const stateReceiverABI = `[{"constant":true,"inputs":[],"name":"SYSTEM_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lastStateId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"syncTime","type":"uint256"},{"internalType":"bytes","name":"recordBytes","type":"bytes"}],"name":"commitState","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]` - -func NewGenesisContractsClient( - chainConfig *params.ChainConfig, - validatorContract, - stateReceiverContract string, - ethAPI *ethapi.PublicBlockChainAPI, -) *GenesisContractsClient { - vABI, _ := abi.JSON(strings.NewReader(validatorsetABI)) - sABI, _ := abi.JSON(strings.NewReader(stateReceiverABI)) - - return &GenesisContractsClient{ - validatorSetABI: vABI, - stateReceiverABI: sABI, - ValidatorContract: validatorContract, - StateReceiverContract: stateReceiverContract, - chainConfig: chainConfig, - ethAPI: ethAPI, - } -} - -func (gc *GenesisContractsClient) CommitState( - event *EventRecordWithTime, - state *state.StateDB, - header *types.Header, - chCtx chainContext, -) error { - eventRecord := event.BuildEventRecord() - recordBytes, err := rlp.EncodeToBytes(eventRecord) - - if err != nil { - return err - } - - method := "commitState" - t := event.Time.Unix() - data, err := gc.stateReceiverABI.Pack(method, big.NewInt(0).SetInt64(t), recordBytes) - - if err != nil { - log.Error("Unable to pack tx for commitState", "error", err) - return err - } - - log.Info("→ committing new state", "eventRecord", event.String()) - - msg := getSystemMessage(common.HexToAddress(gc.StateReceiverContract), data) - if err := applyMessage(msg, state, header, gc.chainConfig, chCtx); err != nil { - return err - } - - return nil -} - -func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int, error) { - blockNr := rpc.BlockNumber(snapshotNumber) - method := "lastStateId" - data, err := gc.stateReceiverABI.Pack(method) - - if err != nil { - log.Error("Unable to pack tx for LastStateId", "error", err) - return nil, err - } - - msgData := (hexutil.Bytes)(data) - toAddress := common.HexToAddress(gc.StateReceiverContract) - gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) - result, err := gc.ethAPI.Call(context.Background(), ethapi.TransactionArgs{ - Gas: &gas, - To: &toAddress, - Data: &msgData, - }, rpc.BlockNumberOrHash{BlockNumber: &blockNr}, nil) - - if err != nil { - return nil, err - } - - var ret = new(*big.Int) - if err := gc.stateReceiverABI.UnpackIntoInterface(ret, method, result); err != nil { - return nil, err - } - - return *ret, nil -} diff --git a/consensus/bor/heimdall.go b/consensus/bor/heimdall.go new file mode 100644 index 0000000000..c97a3cda11 --- /dev/null +++ b/consensus/bor/heimdall.go @@ -0,0 +1,13 @@ +package bor + +import ( + "github.com/ethereum/go-ethereum/consensus/bor/clerk" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" +) + +//go:generate mockgen -destination=../../tests/bor/mocks/IHeimdallClient.go -package=mocks . IHeimdallClient +type IHeimdallClient interface { + StateSyncEvents(fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) + Span(spanID uint64) (*span.HeimdallSpan, error) + Close() +} diff --git a/consensus/bor/heimdall/client.go b/consensus/bor/heimdall/client.go new file mode 100644 index 0000000000..c5e1dfb4cf --- /dev/null +++ b/consensus/bor/heimdall/client.go @@ -0,0 +1,222 @@ +package heimdall + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "sort" + "time" + + "github.com/ethereum/go-ethereum/consensus/bor/clerk" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" + "github.com/ethereum/go-ethereum/log" +) + +// errShutdownDetected is returned if a shutdown was detected +var errShutdownDetected = errors.New("shutdown detected") + +const ( + stateFetchLimit = 50 + apiHeimdallTimeout = 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{} +} + +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" + + fetchSpanFormat = "bor/span/%d" +) + +func (h *HeimdallClient) StateSyncEvents(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) + + response, err := FetchWithRetry[StateSyncEventsResponse](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(spanID uint64) (*span.HeimdallSpan, error) { + url, err := spanURL(h.urlString, spanID) + if err != nil { + return nil, err + } + + response, err := FetchWithRetry[SpanResponse](h.client, url, h.closeCh) + if err != nil { + return nil, err + } + + return &response.Result, nil +} + +// FetchWithRetry returns data from heimdall with retry +func FetchWithRetry[T any](client http.Client, url *url.URL, closeCh chan struct{}) (*T, error) { + // attempt counter + attempt := 1 + result := new(T) + + ctx, cancel := context.WithTimeout(context.Background(), apiHeimdallTimeout) + + // request data once + body, err := internalFetch(ctx, client, url) + + cancel() + + if err == nil && body != nil { + err = json.Unmarshal(body, result) + if err != nil { + return nil, err + } + + return result, nil + } + + // create a new ticker for retrying the request + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + log.Info("Retrying again in 5 seconds to fetch data from Heimdall", "path", url.Path, "attempt", attempt) + attempt++ + select { + case <-closeCh: + log.Debug("Shutdown detected, terminating request") + + return nil, errShutdownDetected + case <-ticker.C: + ctx, cancel = context.WithTimeout(context.Background(), apiHeimdallTimeout) + + body, err = internalFetch(ctx, client, url) + + cancel() + + if err == nil && body != nil { + err = json.Unmarshal(body, result) + if err != nil { + return nil, err + } + + 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 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("Error while fetching data from Heimdall") + } + + // unmarshall data from buffer + if res.StatusCode == 204 { + return nil, nil + } + + // get response + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + return body, nil +} + +// 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/client_test.go b/consensus/bor/heimdall/client_test.go new file mode 100644 index 0000000000..c423a6f659 --- /dev/null +++ b/consensus/bor/heimdall/client_test.go @@ -0,0 +1,35 @@ +package heimdall + +import ( + "testing" +) + +func TestSpanURL(t *testing.T) { + t.Parallel() + + url, err := spanURL("http://bor0", 1) + if err != nil { + t.Fatal("got an error", err) + } + + const expected = "http://bor0/bor/span/1" + + if url.String() != expected { + t.Fatalf("expected URL %q, got %q", url.String(), expected) + } +} + +func TestStateSyncURL(t *testing.T) { + t.Parallel() + + url, err := stateSyncURL("http://bor0", 10, 100) + if err != nil { + t.Fatal("got an error", err) + } + + const expected = "http://bor0/clerk/event-record/list?from-id=10&to-time=100&limit=50" + + if url.String() != expected { + t.Fatalf("expected URL %q, got %q", url.String(), expected) + } +} diff --git a/consensus/bor/heimdall/span/span.go b/consensus/bor/heimdall/span/span.go new file mode 100644 index 0000000000..5bf85fb341 --- /dev/null +++ b/consensus/bor/heimdall/span/span.go @@ -0,0 +1,20 @@ +package span + +import ( + "github.com/ethereum/go-ethereum/consensus/bor/valset" +) + +// Span Bor 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"` +} diff --git a/consensus/bor/heimdall/span/spanner.go b/consensus/bor/heimdall/span/spanner.go new file mode 100644 index 0000000000..7bf6e350ee --- /dev/null +++ b/consensus/bor/heimdall/span/spanner.go @@ -0,0 +1,200 @@ +package span + +import ( + "context" + "encoding/hex" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/bor/abi" + "github.com/ethereum/go-ethereum/consensus/bor/api" + "github.com/ethereum/go-ethereum/consensus/bor/statefull" + "github.com/ethereum/go-ethereum/consensus/bor/valset" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" +) + +type ChainSpanner struct { + ethAPI api.Caller + validatorSet abi.ABI + chainConfig *params.ChainConfig + validatorContractAddress common.Address +} + +func NewChainSpanner(ethAPI api.Caller, validatorSet abi.ABI, chainConfig *params.ChainConfig, validatorContractAddress common.Address) *ChainSpanner { + return &ChainSpanner{ + ethAPI: ethAPI, + validatorSet: validatorSet, + chainConfig: chainConfig, + validatorContractAddress: validatorContractAddress, + } +} + +// GetCurrentSpan get current span from contract +func (c *ChainSpanner) GetCurrentSpan(headerHash common.Hash) (*Span, error) { + // block + blockNr := rpc.BlockNumberOrHashWithHash(headerHash, false) + + // method + method := "getCurrentSpan" + + data, err := c.validatorSet.Pack(method) + if err != nil { + log.Error("Unable to pack tx for getCurrentSpan", "error", err) + + return nil, err + } + + msgData := (hexutil.Bytes)(data) + toAddress := c.validatorContractAddress + gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) + + // todo: would we like to have a timeout here? + result, err := c.ethAPI.Call(context.Background(), ethapi.TransactionArgs{ + Gas: &gas, + To: &toAddress, + Data: &msgData, + }, blockNr, nil) + if err != nil { + return nil, err + } + + // span result + ret := new(struct { + Number *big.Int + StartBlock *big.Int + EndBlock *big.Int + }) + + if err := c.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 +} + +// GetCurrentValidators get current validators +func (c *ChainSpanner) GetCurrentValidators(headerHash common.Hash, blockNumber uint64) ([]*valset.Validator, error) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // method + const method = "getBorValidators" + + data, err := c.validatorSet.Pack(method, big.NewInt(0).SetUint64(blockNumber)) + if err != nil { + log.Error("Unable to pack tx for getValidator", "error", err) + return nil, err + } + + // call + msgData := (hexutil.Bytes)(data) + toAddress := c.validatorContractAddress + gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2)) + + // block + blockNr := rpc.BlockNumberOrHashWithHash(headerHash, false) + + result, err := c.ethAPI.Call(ctx, ethapi.TransactionArgs{ + Gas: &gas, + To: &toAddress, + Data: &msgData, + }, blockNr, nil) + if err != nil { + panic(err) + } + + var ( + ret0 = new([]common.Address) + ret1 = new([]*big.Int) + ) + + out := &[]interface{}{ + ret0, + ret1, + } + + if err := c.validatorSet.UnpackIntoInterface(out, method, result); err != nil { + return nil, err + } + + valz := make([]*valset.Validator, len(*ret0)) + for i, a := range *ret0 { + valz[i] = &valset.Validator{ + Address: a, + VotingPower: (*ret1)[i].Int64(), + } + } + + return valz, nil +} + +const method = "commitSpan" + +func (c *ChainSpanner) CommitSpan(heimdallSpan HeimdallSpan, state *state.StateDB, header *types.Header, chainContext core.ChainContext) error { + // 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.Info("✅ Committing new span", + "id", heimdallSpan.ID, + "startBlock", heimdallSpan.StartBlock, + "endBlock", heimdallSpan.EndBlock, + "validatorBytes", hex.EncodeToString(validatorBytes), + "producerBytes", hex.EncodeToString(producerBytes), + ) + + 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 + } + + // get system message + msg := statefull.GetSystemMessage(c.validatorContractAddress, data) + + // apply message + _, err = statefull.ApplyMessage(msg, state, header, c.chainConfig, chainContext) + + return err +} diff --git a/consensus/bor/rest.go b/consensus/bor/rest.go deleted file mode 100644 index 49ecb38af5..0000000000 --- a/consensus/bor/rest.go +++ /dev/null @@ -1,177 +0,0 @@ -package bor - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "sort" - "time" - - "github.com/ethereum/go-ethereum/log" -) - -var ( - stateFetchLimit = 50 -) - -// ResponseWithHeight defines a response object type that wraps an original -// response with a height. -type ResponseWithHeight struct { - Height string `json:"height"` - Result json.RawMessage `json:"result"` -} - -type IHeimdallClient interface { - Fetch(path string, query string) (*ResponseWithHeight, error) - FetchWithRetry(path string, query string) (*ResponseWithHeight, error) - FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error) - Close() -} - -type HeimdallClient struct { - urlString string - client http.Client - closeCh chan struct{} -} - -func NewHeimdallClient(urlString string) (*HeimdallClient, error) { - h := &HeimdallClient{ - urlString: urlString, - client: http.Client{ - Timeout: 5 * time.Second, - }, - closeCh: make(chan struct{}), - } - - return h, nil -} - -func (h *HeimdallClient) FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error) { - eventRecords := make([]*EventRecordWithTime, 0) - - for { - queryParams := fmt.Sprintf("from-id=%d&to-time=%d&limit=%d", fromID, to, stateFetchLimit) - log.Info("Fetching state sync events", "queryParams", queryParams) - response, err := h.FetchWithRetry("clerk/event-record/list", queryParams) - - if err != nil { - return nil, err - } - - var _eventRecords []*EventRecordWithTime - - if response.Result == nil { // status 204 - break - } - - if err := json.Unmarshal(response.Result, &_eventRecords); err != nil { - return nil, err - } - - eventRecords = append(eventRecords, _eventRecords...) - - if len(_eventRecords) < stateFetchLimit { - break - } - - fromID += uint64(stateFetchLimit) - } - - sort.SliceStable(eventRecords, func(i, j int) bool { - return eventRecords[i].ID < eventRecords[j].ID - }) - - return eventRecords, nil -} - -// Fetch fetches response from heimdall -func (h *HeimdallClient) Fetch(rawPath string, rawQuery string) (*ResponseWithHeight, error) { - u, err := url.Parse(h.urlString) - if err != nil { - return nil, err - } - - u.Path = rawPath - u.RawQuery = rawQuery - - return h.internalFetch(u) -} - -// FetchWithRetry returns data from heimdall with retry -func (h *HeimdallClient) FetchWithRetry(rawPath string, rawQuery string) (*ResponseWithHeight, error) { - u, err := url.Parse(h.urlString) - if err != nil { - return nil, err - } - - u.Path = rawPath - u.RawQuery = rawQuery - - // attempt counter - attempt := 1 - - // request data once - res, err := h.internalFetch(u) - if err == nil && res != nil { - return res, nil - } - - // create a new ticker for retrying the request - ticker := time.NewTicker(5 * time.Second) - defer ticker.Stop() - - for { - log.Info("Retrying again in 5 seconds to fetch data from Heimdall", "path", u.Path, "attempt", attempt) - attempt++ - select { - case <-h.closeCh: - log.Debug("Shutdown detected, terminating request") - return nil, errShutdownDetected - case <-ticker.C: - res, err := h.internalFetch(u) - if err == nil && res != nil { - return res, nil - } - } - } -} - -// internal fetch method -func (h *HeimdallClient) internalFetch(u *url.URL) (*ResponseWithHeight, error) { - res, err := h.client.Get(u.String()) // nolint: noctx - if err != nil { - return nil, err - } - defer res.Body.Close() - - // check status code - if res.StatusCode != 200 && res.StatusCode != 204 { - return nil, fmt.Errorf("Error while fetching data from Heimdall") - } - - // unmarshall data from buffer - var response ResponseWithHeight - if res.StatusCode == 204 { - return &response, nil - } - - // get response - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if err := json.Unmarshal(body, &response); err != nil { - return nil, err - } - - return &response, nil -} - -// Close sends a signal to stop the running process -func (h *HeimdallClient) Close() { - close(h.closeCh) - h.client.CloseIdleConnections() -} diff --git a/consensus/bor/snapshot.go b/consensus/bor/snapshot.go index 606c28340b..f71ceae0ad 100644 --- a/consensus/bor/snapshot.go +++ b/consensus/bor/snapshot.go @@ -3,24 +3,24 @@ package bor import ( "encoding/json" + "github.com/ethereum/go-ethereum/consensus/bor/valset" + lru "github.com/hashicorp/golang-lru" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/params" ) // Snapshot is the state of the authorization voting at a given point in time. type Snapshot struct { config *params.BorConfig // Consensus engine parameters to fine tune behavior - ethAPI *ethapi.PublicBlockChainAPI - sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover + sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover Number uint64 `json:"number"` // Block number where the snapshot was created Hash common.Hash `json:"hash"` // Block hash where the snapshot was created - ValidatorSet *ValidatorSet `json:"validatorSet"` // Validator set at this moment + ValidatorSet *valset.ValidatorSet `json:"validatorSet"` // Validator set at this moment Recents map[uint64]common.Address `json:"recents"` // Set of recent signers for spam protections } @@ -32,16 +32,14 @@ func newSnapshot( sigcache *lru.ARCCache, number uint64, hash common.Hash, - validators []*Validator, - ethAPI *ethapi.PublicBlockChainAPI, + validators []*valset.Validator, ) *Snapshot { snap := &Snapshot{ config: config, - ethAPI: ethAPI, sigcache: sigcache, Number: number, Hash: hash, - ValidatorSet: NewValidatorSet(validators), + ValidatorSet: valset.NewValidatorSet(validators), Recents: make(map[uint64]common.Address), } @@ -49,7 +47,7 @@ func newSnapshot( } // loadSnapshot loads an existing snapshot from the database. -func loadSnapshot(config *params.BorConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash, ethAPI *ethapi.PublicBlockChainAPI) (*Snapshot, error) { +func loadSnapshot(config *params.BorConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) { blob, err := db.Get(append([]byte("bor-"), hash[:]...)) if err != nil { return nil, err @@ -63,10 +61,9 @@ func loadSnapshot(config *params.BorConfig, sigcache *lru.ARCCache, db ethdb.Dat snap.config = config snap.sigcache = sigcache - snap.ethAPI = ethAPI // update total voting power - if err := snap.ValidatorSet.updateTotalVotingPower(); err != nil { + if err := snap.ValidatorSet.UpdateTotalVotingPower(); err != nil { return nil, err } @@ -87,7 +84,6 @@ func (s *Snapshot) store(db ethdb.Database) error { func (s *Snapshot) copy() *Snapshot { cpy := &Snapshot{ config: s.config, - ethAPI: s.ethAPI, sigcache: s.sigcache, Number: s.Number, Hash: s.Hash, @@ -155,7 +151,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, _ := ParseValidators(validatorBytes) + newVals, _ := valset.ParseValidators(validatorBytes) v := getUpdatedValidatorSet(snap.ValidatorSet.Copy(), newVals) v.IncrementProposerPriority(1) snap.ValidatorSet = v diff --git a/consensus/bor/snapshot_test.go b/consensus/bor/snapshot_test.go index 2bbbc32123..1a3e967613 100644 --- a/consensus/bor/snapshot_test.go +++ b/consensus/bor/snapshot_test.go @@ -7,8 +7,11 @@ import ( "time" "github.com/stretchr/testify/assert" + "pgregory.net/rapid" "github.com/ethereum/go-ethereum/common" + unique "github.com/ethereum/go-ethereum/common/set" + "github.com/ethereum/go-ethereum/consensus/bor/valset" ) const ( @@ -19,7 +22,7 @@ func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) { t.Parallel() validators := buildRandomValidatorSet(numVals) - validatorSet := NewValidatorSet(validators) + validatorSet := valset.NewValidatorSet(validators) snap := Snapshot{ ValidatorSet: validatorSet, } @@ -27,7 +30,6 @@ func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) { // proposer is signer signer := validatorSet.Proposer.Address successionNumber, err := snap.GetSignerSuccessionNumber(signer) - if err != nil { t.Fatalf("%s", err) } @@ -41,20 +43,19 @@ func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) { 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 signer := snap.ValidatorSet.Validators[signerIndex].Address successionNumber, err := snap.GetSignerSuccessionNumber(signer) - if err != nil { t.Fatalf("%s", err) } @@ -71,13 +72,12 @@ func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) { // 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 signer := snap.ValidatorSet.Validators[signerIndex].Address successionNumber, err := snap.GetSignerSuccessionNumber(signer) - if err != nil { t.Fatalf("%s", err) } @@ -90,14 +90,18 @@ func TestGetSignerSuccessionNumber_ProposerNotFound(t *testing.T) { 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) + e, ok := err.(*UnauthorizedProposerError) assert.True(t, ok) assert.Equal(t, dummyProposerAddress.Bytes(), e.Proposer) @@ -108,7 +112,7 @@ func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) { validators := buildRandomValidatorSet(numVals) snap := Snapshot{ - ValidatorSet: NewValidatorSet(validators), + ValidatorSet: valset.NewValidatorSet(validators), } dummySignerAddress := randomAddress() _, err := snap.GetSignerSuccessionNumber(dummySignerAddress) @@ -119,21 +123,22 @@ func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) { } // nolint: unparam -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) + valAddrs := randomAddresses(numVals) for i := 0; i < numVals; i++ { - validators[i] = &Validator{ - Address: randomAddress(), + validators[i] = &valset.Validator{ + Address: valAddrs[i], // cannot process validators with voting power 0, hence +1 VotingPower: int64(rand.Intn(99) + 1), } } // sort validators by address, which is what NewValidatorSet also does - sort.Sort(ValidatorsByAddress(validators)) + sort.Sort(valset.ValidatorsByAddress(validators)) return validators } @@ -144,3 +149,51 @@ func randomAddress() common.Address { return common.BytesToAddress(bytes) } + +func randomAddresses(n int) []common.Address { + if n <= 0 { + return []common.Address{} + } + + addrs := make([]common.Address, 0, n) + addrsSet := make(map[common.Address]struct{}, n) + + var ( + addr common.Address + exist bool + ) + + bytes := make([]byte, 32) + + for { + rand.Read(bytes) + + addr = common.BytesToAddress(bytes) + + _, exist = addrsSet[addr] + if !exist { + addrs = append(addrs, addr) + + addrsSet[addr] = struct{}{} + } + + if len(addrs) == n { + return addrs + } + } +} + +func TestRandomAddresses(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(t *rapid.T) { + length := rapid.IntMax(100).Draw(t, "length").(int) + + addrs := randomAddresses(length) + addressSet := unique.New(addrs) + + if len(addrs) != len(addressSet) { + t.Fatalf("length of unique addresses %d, expected %d", len(addressSet), len(addrs)) + } + }) +} diff --git a/consensus/bor/span.go b/consensus/bor/span.go index 2fd0cf1079..4867635b8e 100644 --- a/consensus/bor/span.go +++ b/consensus/bor/span.go @@ -1,16 +1,17 @@ package bor -// Span represents a current bor span -type Span struct { - ID uint64 `json:"span_id" yaml:"span_id"` - StartBlock uint64 `json:"start_block" yaml:"start_block"` - EndBlock uint64 `json:"end_block" yaml:"end_block"` -} +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" + "github.com/ethereum/go-ethereum/consensus/bor/valset" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" +) -// 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"` +//go:generate mockgen -destination=./span_mock.go -package=bor . Spanner +type Spanner interface { + GetCurrentSpan(headerHash common.Hash) (*span.Span, error) + GetCurrentValidators(headerHash common.Hash, blockNumber uint64) ([]*valset.Validator, error) + CommitSpan(heimdallSpan span.HeimdallSpan, state *state.StateDB, header *types.Header, chainContext core.ChainContext) error } diff --git a/consensus/bor/span_mock.go b/consensus/bor/span_mock.go new file mode 100644 index 0000000000..12ed945234 --- /dev/null +++ b/consensus/bor/span_mock.go @@ -0,0 +1,84 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: Spanner) + +// Package bor is a generated GoMock package. +package bor + +import ( + reflect "reflect" + + common "github.com/ethereum/go-ethereum/common" + span "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" + valset "github.com/ethereum/go-ethereum/consensus/bor/valset" + core "github.com/ethereum/go-ethereum/core" + state "github.com/ethereum/go-ethereum/core/state" + types "github.com/ethereum/go-ethereum/core/types" + gomock "github.com/golang/mock/gomock" +) + +// 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 *state.StateDB, arg2 *types.Header, arg3 core.ChainContext) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitSpan", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitSpan indicates an expected call of CommitSpan. +func (mr *MockSpannerMockRecorder) CommitSpan(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitSpan", reflect.TypeOf((*MockSpanner)(nil).CommitSpan), arg0, arg1, arg2, arg3) +} + +// GetCurrentSpan mocks base method. +func (m *MockSpanner) GetCurrentSpan(arg0 common.Hash) (*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 common.Hash, arg1 uint64) ([]*valset.Validator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCurrentValidators", arg0, arg1) + 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 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidators", reflect.TypeOf((*MockSpanner)(nil).GetCurrentValidators), arg0, arg1) +} diff --git a/consensus/bor/statefull/processor.go b/consensus/bor/statefull/processor.go new file mode 100644 index 0000000000..e30fb4fd21 --- /dev/null +++ b/consensus/bor/statefull/processor.go @@ -0,0 +1,93 @@ +package statefull + +import ( + "math" + "math/big" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" +) + +var systemAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") + +type ChainContext struct { + Chain consensus.ChainHeaderReader + Bor consensus.Engine +} + +func (c ChainContext) Engine() consensus.Engine { + return c.Bor +} + +func (c ChainContext) GetHeader(hash common.Hash, number uint64) *types.Header { + return c.Chain.GetHeader(hash, number) +} + +// callmsg implements core.Message to allow passing it as a transaction simulator. +type callmsg struct { + ethereum.CallMsg +} + +func (m callmsg) From() common.Address { return m.CallMsg.From } +func (m callmsg) Nonce() uint64 { return 0 } +func (m callmsg) CheckNonce() bool { return false } +func (m callmsg) To() *common.Address { return m.CallMsg.To } +func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } +func (m callmsg) Value() *big.Int { return m.CallMsg.Value } +func (m callmsg) Data() []byte { return m.CallMsg.Data } + +// get system message +func GetSystemMessage(toAddress common.Address, data []byte) callmsg { + return callmsg{ + ethereum.CallMsg{ + From: systemAddress, + Gas: math.MaxUint64 / 2, + GasPrice: big.NewInt(0), + Value: big.NewInt(0), + To: &toAddress, + Data: data, + }, + } +} + +// apply message +func ApplyMessage( + msg callmsg, + state *state.StateDB, + header *types.Header, + chainConfig *params.ChainConfig, + chainContext core.ChainContext, +) (uint64, error) { + initialGas := msg.Gas() + + // Create a new context to be used in the EVM environment + blockContext := core.NewEVMBlockContext(header, chainContext, &header.Coinbase) + + // Create a new environment which holds all relevant information + // about the transaction and calling mechanisms. + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, state, chainConfig, vm.Config{}) + + // Apply the transaction to the current state (included in the env) + _, gasLeft, err := vmenv.Call( + vm.AccountRef(msg.From()), + *msg.To(), + msg.Data(), + msg.Gas(), + msg.Value(), + ) + // Update the state with pending changes + if err != nil { + state.Finalise(true) + } + + gasUsed := initialGas - gasLeft + + return gasUsed, nil +} diff --git a/consensus/bor/validators_getter.go b/consensus/bor/validators_getter.go new file mode 100644 index 0000000000..90d1fccf6e --- /dev/null +++ b/consensus/bor/validators_getter.go @@ -0,0 +1,11 @@ +package bor + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/bor/valset" +) + +//go:generate mockgen -destination=./validators_getter_mock.go -package=bor . ValidatorsGetter +type ValidatorsGetter interface { + GetCurrentValidators(headerHash common.Hash, blockNumber uint64) ([]*valset.Validator, error) +} diff --git a/consensus/bor/validators_getter_mock.go b/consensus/bor/validators_getter_mock.go new file mode 100644 index 0000000000..ad99489d8e --- /dev/null +++ b/consensus/bor/validators_getter_mock.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: ValidatorsGetter) + +// Package bor is a generated GoMock package. +package bor + +import ( + reflect "reflect" + + common "github.com/ethereum/go-ethereum/common" + valset "github.com/ethereum/go-ethereum/consensus/bor/valset" + gomock "github.com/golang/mock/gomock" +) + +// MockValidatorsGetter is a mock of ValidatorsGetter interface. +type MockValidatorsGetter struct { + ctrl *gomock.Controller + recorder *MockValidatorsGetterMockRecorder +} + +// MockValidatorsGetterMockRecorder is the mock recorder for MockValidatorsGetter. +type MockValidatorsGetterMockRecorder struct { + mock *MockValidatorsGetter +} + +// NewMockValidatorsGetter creates a new mock instance. +func NewMockValidatorsGetter(ctrl *gomock.Controller) *MockValidatorsGetter { + mock := &MockValidatorsGetter{ctrl: ctrl} + mock.recorder = &MockValidatorsGetterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockValidatorsGetter) EXPECT() *MockValidatorsGetterMockRecorder { + return m.recorder +} + +// GetCurrentValidators mocks base method. +func (m *MockValidatorsGetter) GetCurrentValidators(arg0 common.Hash, arg1 uint64) ([]*valset.Validator, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCurrentValidators", arg0, arg1) + ret0, _ := ret[0].([]*valset.Validator) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCurrentValidators indicates an expected call of GetCurrentValidators. +func (mr *MockValidatorsGetterMockRecorder) GetCurrentValidators(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidators", reflect.TypeOf((*MockValidatorsGetter)(nil).GetCurrentValidators), arg0, arg1) +} diff --git a/consensus/bor/valset/error.go b/consensus/bor/valset/error.go new file mode 100644 index 0000000000..37add21683 --- /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 99% rename from consensus/bor/validator.go rename to consensus/bor/valset/validator.go index 57905fdf89..250206c1f3 100644 --- a/consensus/bor/validator.go +++ b/consensus/bor/valset/validator.go @@ -1,8 +1,7 @@ -package bor +package valset import ( "bytes" - // "encoding/json" "errors" "fmt" "math/big" diff --git a/consensus/bor/validator_set.go b/consensus/bor/valset/validator_set.go similarity index 99% rename from consensus/bor/validator_set.go rename to consensus/bor/valset/validator_set.go index ee14ef2cb1..19e6c681fe 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 @@ -55,8 +55,8 @@ type ValidatorSet struct { // function panics. func NewValidatorSet(valz []*Validator) *ValidatorSet { vals := &ValidatorSet{} - err := vals.updateWithChangeSet(valz, false) + err := vals.updateWithChangeSet(valz, false) if err != nil { panic(fmt.Sprintf("cannot create validator set: %s", err)) } @@ -281,7 +281,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 @@ -302,7 +302,7 @@ 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) } @@ -602,7 +602,7 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes vals.applyUpdates(updates) vals.applyRemovals(deletes) - if err := vals.updateTotalVotingPower(); err != nil { + if err := vals.UpdateTotalVotingPower(); err != nil { return err } diff --git a/consensus/merger.go b/consensus/merger.go index ffbcbf2b85..c435ecf91e 100644 --- a/consensus/merger.go +++ b/consensus/merger.go @@ -45,12 +45,14 @@ type Merger struct { // NewMerger creates a new Merger which stores its transition status in the provided db. func NewMerger(db ethdb.KeyValueStore) *Merger { var status transitionStatus + blob := rawdb.ReadTransitionStatus(db) if len(blob) != 0 { if err := rlp.DecodeBytes(blob, &status); err != nil { log.Crit("Failed to decode the transition status", "err", err) } } + return &Merger{ db: db, status: status, diff --git a/core/bench_test.go b/core/bench_test.go index 959979763d..ad6179d0a8 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -305,7 +305,8 @@ func benchReadChain(b *testing.B, full bool, count uint64) { } makeChainForBench(db, full, count) db.Close() - cacheConfig := *defaultCacheConfig + + cacheConfig := *DefaultCacheConfig cacheConfig.TrieDirtyDisabled = true b.ReportAllocs() diff --git a/core/blockchain.go b/core/blockchain.go index 1b11c781c5..8a4c581f66 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -27,6 +27,8 @@ import ( "sync/atomic" "time" + lru "github.com/hashicorp/golang-lru" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/prque" @@ -43,7 +45,6 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" - lru "github.com/hashicorp/golang-lru" ) var ( @@ -133,9 +134,9 @@ type CacheConfig struct { SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } -// defaultCacheConfig are the default caching values if none are specified by the +// DefaultCacheConfig are the default caching values if none are specified by the // user (also used during testing). -var defaultCacheConfig = &CacheConfig{ +var DefaultCacheConfig = &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, @@ -222,7 +223,7 @@ type BlockChain struct { // and Processor. func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) { if cacheConfig == nil { - cacheConfig = defaultCacheConfig + cacheConfig = DefaultCacheConfig } bodyCache, _ := lru.New(bodyCacheLimit) bodyRLPCache, _ := lru.New(bodyCacheLimit) diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index b93444b8cd..f61f930496 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -397,10 +398,25 @@ func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscr return bc.scope.Track(bc.blockProcFeed.Subscribe(ch)) } +// Snaps retrieves the snapshot tree. +func (bc *BlockChain) Snaps() *snapshot.Tree { + return bc.snaps +} + +// DB retrieves the blockchain database. +func (bc *BlockChain) DB() ethdb.Database { + return bc.db +} + // // Bor related changes // +type BorStateSyncer interface { + SetStateSync(stateData []*types.StateSyncData) + SubscribeStateSyncEvent(ch chan<- StateSyncEvent) event.Subscription +} + // SetStateSync set sync data in state_data func (bc *BlockChain) SetStateSync(stateData []*types.StateSyncData) { bc.stateSyncData = stateData diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 37a1a42d0c..de97000918 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -920,7 +920,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { archiveDb, delfn := makeDb() defer delfn() - archiveCaching := *defaultCacheConfig + archiveCaching := *DefaultCacheConfig archiveCaching.TrieDirtyDisabled = true archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) diff --git a/core/chain_makers.go b/core/chain_makers.go index c7bf60a4b0..4b2d2082df 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" ) @@ -334,7 +335,10 @@ func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethd } type fakeChainReader struct { - config *params.ChainConfig + config *params.ChainConfig + stateSyncData []*types.StateSyncData + stateSyncFeed event.Feed + scope event.SubscriptionScope } // Config returns the chain configuration. @@ -348,3 +352,13 @@ func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil } func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { return nil } + +// SetStateSync set sync data in state_data +func (cr *fakeChainReader) SetStateSync(stateData []*types.StateSyncData) { + cr.stateSyncData = stateData +} + +// SubscribeStateSyncEvent registers a subscription of StateSyncEvent. +func (cr *fakeChainReader) SubscribeStateSyncEvent(ch chan<- StateSyncEvent) event.Subscription { + return cr.scope.Track(cr.stateSyncFeed.Subscribe(ch)) +} diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 7d6939e1dc..158e8c1eeb 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -21,6 +21,8 @@ import ( "math/big" "testing" + "golang.org/x/crypto/sha3" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" @@ -32,7 +34,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" - "golang.org/x/crypto/sha3" ) // TestStateProcessorErrors tests the output from the 'core' errors @@ -308,7 +309,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr header := &types.Header{ ParentHash: parent.Hash(), Coinbase: parent.Coinbase(), - Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{ + Difficulty: engine.CalcDifficulty(&fakeChainReader{config: config}, parent.Time()+10, &types.Header{ Number: parent.Number(), Time: parent.Time(), Difficulty: parent.Difficulty(), diff --git a/core/blockchain_repair_test.go b/core/tests/blockchain_repair_test.go similarity index 93% rename from core/blockchain_repair_test.go rename to core/tests/blockchain_repair_test.go index 9133671796..b52bf76fd9 100644 --- a/core/blockchain_repair_test.go +++ b/core/tests/blockchain_repair_test.go @@ -18,7 +18,7 @@ // the database in some strange state with gaps in the chain, nor with block data // dangling in the future. -package core +package tests import ( "io/ioutil" @@ -27,12 +27,22 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" + + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/bor" + "github.com/ethereum/go-ethereum/consensus/bor/api" + "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/tests/bor/mocks" ) // Tests a recovery for a short canonical chain where a recent block was already @@ -1750,7 +1760,17 @@ func testLongReorgedSnapSyncingDeepRepair(t *testing.T, snapshots bool) { }, snapshots) } +var ( + testKey1, _ = crypto.GenerateKey() + testAddress1 = crypto.PubkeyToAddress(testKey1.PublicKey) + + testKey2, _ = crypto.GenerateKey() + testAddress2 = crypto.PubkeyToAddress(testKey2.PublicKey) //nolint:unused,varcheck +) + func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { + t.Skip("need to add a proper signer for Bor consensus") + // It's hard to follow the test case, visualize the input //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // fmt.Println(tt.dump(true)) @@ -1768,47 +1788,104 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { } defer db.Close() // Might double close, should be fine + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ethAPIMock := api.NewMockCaller(ctrl) + ethAPIMock.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + + spanner := bor.NewMockSpanner(ctrl) + spanner.EXPECT().GetCurrentValidators(gomock.Any(), gomock.Any()).Return([]*valset.Validator{ + { + ID: 0, + Address: common.Address{0x1}, + VotingPower: 100, + ProposerPriority: 0, + }, + }, nil).AnyTimes() + + heimdallClientMock := mocks.NewMockIHeimdallClient(ctrl) + heimdallClientMock.EXPECT().Close().Times(1) + + contractMock := bor.NewMockGenesisContract(ctrl) + // Initialize a fresh chain var ( - genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) - engine = ethash.NewFullFaker() - config = &CacheConfig{ + gspec = &core.Genesis{ + Config: params.BorUnittestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + config = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, // Disable snapshot by default } ) + + engine := miner.NewFakeBor(t, db, params.BorUnittestChainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock) defer engine.Close() + + engineBorInternal, ok := engine.(*bor.Bor) + if ok { + gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) + copy(gspec.ExtraData[32:32+common.AddressLength], testAddress1.Bytes()) + + engineBorInternal.Authorize(testAddress1, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), testKey1) + }) + } + + genesis := gspec.MustCommit(db) + if snapshots { config.SnapshotLimit = 256 config.SnapshotWait = true } - chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + + chain, err := core.NewBlockChain(db, config, params.BorUnittestChainConfig, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } + // If sidechain blocks are needed, make a light chain and import it var sideblocks types.Blocks if tt.sidechainBlocks > 0 { - sideblocks, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) { - b.SetCoinbase(common.Address{0x01}) + sideblocks, _ = core.GenerateChain(params.BorUnittestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *core.BlockGen) { + b.SetCoinbase(testAddress1) + + if bor.IsSprintStart(b.Number().Uint64(), params.BorUnittestChainConfig.Bor.Sprint) { + b.SetExtra(gspec.ExtraData) + } else { + b.SetExtra(make([]byte, 32+crypto.SignatureLength)) + } }) if _, err := chain.InsertChain(sideblocks); err != nil { t.Fatalf("Failed to import side chain: %v", err) } } - canonblocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) { + + canonblocks, _ := core.GenerateChain(params.BorUnittestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{0x02}) b.SetDifficulty(big.NewInt(1000000)) + + if bor.IsSprintStart(b.Number().Uint64(), params.BorUnittestChainConfig.Bor.Sprint) { + b.SetExtra(gspec.ExtraData) + } else { + b.SetExtra(make([]byte, 32+crypto.SignatureLength)) + } }) if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } if tt.commitBlock > 0 { - chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil) + err = chain.StateCache().TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil) + if err != nil { + t.Fatal("on trieDB.Commit", err) + } + if snapshots { - if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { + if err := chain.Snaps().Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { t.Fatalf("Failed to flatten snapshots: %v", err) } } @@ -1837,7 +1914,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { } defer db.Close() - newChain, err := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + newChain, err := core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -1888,19 +1965,21 @@ func TestIssue23496(t *testing.T) { if err != nil { t.Fatalf("Failed to create temporary datadir: %v", err) } + os.RemoveAll(datadir) db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } + defer db.Close() // Might double close, should be fine // Initialize a fresh chain var ( - genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis = (&core.Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) engine = ethash.NewFullFaker() - config = &CacheConfig{ + config = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, @@ -1908,11 +1987,13 @@ func TestIssue23496(t *testing.T) { SnapshotWait: true, } ) - chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + + chain, err := core.NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } - blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), 4, func(i int, b *BlockGen) { + + blocks, _ := core.GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), 4, func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{0x02}) b.SetDifficulty(big.NewInt(1000000)) }) @@ -1921,13 +2002,18 @@ func TestIssue23496(t *testing.T) { if _, err := chain.InsertChain(blocks[:1]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } - chain.stateCache.TrieDB().Commit(blocks[0].Root(), true, nil) + + err = chain.StateCache().TrieDB().Commit(blocks[0].Root(), true, nil) + if err != nil { + t.Fatal("on trieDB.Commit", err) + } // Insert block B2 and commit the snapshot into disk if _, err := chain.InsertChain(blocks[1:2]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } - if err := chain.snaps.Cap(blocks[1].Root(), 0); err != nil { + + if err := chain.Snaps().Cap(blocks[1].Root(), 0); err != nil { t.Fatalf("Failed to flatten snapshots: %v", err) } @@ -1935,7 +2021,11 @@ func TestIssue23496(t *testing.T) { if _, err := chain.InsertChain(blocks[2:3]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } - chain.stateCache.TrieDB().Commit(blocks[2].Root(), true, nil) + + err = chain.StateCache().TrieDB().Commit(blocks[2].Root(), true, nil) + if err != nil { + t.Fatal("on trieDB.Commit", err) + } // Insert the remaining blocks if _, err := chain.InsertChain(blocks[3:]); err != nil { @@ -1950,20 +2040,24 @@ func TestIssue23496(t *testing.T) { if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } + defer db.Close() - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + chain, err = core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } + defer chain.Stop() if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) { t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4) } + if head := chain.CurrentFastBlock(); head.NumberU64() != uint64(4) { t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), uint64(4)) } + if head := chain.CurrentBlock(); head.NumberU64() != uint64(1) { t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), uint64(1)) } @@ -1972,15 +2066,19 @@ func TestIssue23496(t *testing.T) { if _, err := chain.InsertChain(blocks[1:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) } + if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) { t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4) } + if head := chain.CurrentFastBlock(); head.NumberU64() != uint64(4) { t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), uint64(4)) } + if head := chain.CurrentBlock(); head.NumberU64() != uint64(4) { t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), uint64(4)) } + if layer := chain.Snapshots().Snapshot(blocks[2].Root()); layer == nil { t.Error("Failed to regenerate the snapshot of known state") } diff --git a/core/blockchain_sethead_test.go b/core/tests/blockchain_sethead_test.go similarity index 98% rename from core/blockchain_sethead_test.go rename to core/tests/blockchain_sethead_test.go index b2b3a058a4..3a2d87c4eb 100644 --- a/core/blockchain_sethead_test.go +++ b/core/tests/blockchain_sethead_test.go @@ -17,7 +17,7 @@ // Tests that setting the chain head backwards doesn't leave the database in some // strange state with gaps in the chain, nor with block data dangling in the future. -package core +package tests import ( "fmt" @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -1959,79 +1960,98 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { if err != nil { t.Fatalf("Failed to create temporary datadir: %v", err) } + os.RemoveAll(datadir) db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } + defer db.Close() // Initialize a fresh chain var ( - genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis = (&core.Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) engine = ethash.NewFullFaker() - config = &CacheConfig{ + config = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, // Disable snapshot } ) + if snapshots { config.SnapshotLimit = 256 config.SnapshotWait = true } - chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + + chain, err := core.NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } + // If sidechain blocks are needed, make a light chain and import it var sideblocks types.Blocks if tt.sidechainBlocks > 0 { - sideblocks, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) { + sideblocks, _ = core.GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{0x01}) }) + if _, err := chain.InsertChain(sideblocks); err != nil { t.Fatalf("Failed to import side chain: %v", err) } } - canonblocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) { + + canonblocks, _ := core.GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{0x02}) b.SetDifficulty(big.NewInt(1000000)) }) + if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } + if tt.commitBlock > 0 { - chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil) + err = chain.StateCache().TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil) + if err != nil { + t.Fatal("on trieDB.Commit", err) + } + if snapshots { - if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { + if err := chain.Snaps().Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { t.Fatalf("Failed to flatten snapshots: %v", err) } } } + if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) } + // Manually dereference anything not committed to not have to work with 128+ tries for _, block := range sideblocks { - chain.stateCache.TrieDB().Dereference(block.Root()) + chain.StateCache().TrieDB().Dereference(block.Root()) } + for _, block := range canonblocks { - chain.stateCache.TrieDB().Dereference(block.Root()) + chain.StateCache().TrieDB().Dereference(block.Root()) } + // Force run a freeze cycle type freezer interface { Freeze(threshold uint64) error Ancients() (uint64, error) } + db.(freezer).Freeze(tt.freezeThreshold) // Set the simulated pivot block if tt.pivotBlock != nil { rawdb.WriteLastPivotNumber(db, *tt.pivotBlock) } + // Set the head of the chain back to the requested number chain.SetHead(tt.setheadBlock) @@ -2044,12 +2064,15 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader { t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadHeader) } + if head := chain.CurrentFastBlock(); head.NumberU64() != tt.expHeadFastBlock { t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadFastBlock) } + if head := chain.CurrentBlock(); head.NumberU64() != tt.expHeadBlock { t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadBlock) } + if frozen, err := db.(freezer).Ancients(); err != nil { t.Errorf("Failed to retrieve ancient count: %v\n", err) } else if int(frozen) != tt.expFrozen { @@ -2059,7 +2082,8 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { // verifyNoGaps checks that there are no gaps after the initial set of blocks in // the database and errors if found. -func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks) { +//nolint:gocognit +func verifyNoGaps(t *testing.T, chain *core.BlockChain, canonical bool, inserted types.Blocks) { t.Helper() var end uint64 @@ -2111,7 +2135,8 @@ func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted type // verifyCutoff checks that there are no chain data available in the chain after // the specified limit, but that it is available before. -func verifyCutoff(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks, head int) { +//nolint:gocognit +func verifyCutoff(t *testing.T, chain *core.BlockChain, canonical bool, inserted types.Blocks, head int) { t.Helper() for i := 1; i <= len(inserted); i++ { diff --git a/core/blockchain_snapshot_test.go b/core/tests/blockchain_snapshot_test.go similarity index 87% rename from core/blockchain_snapshot_test.go rename to core/tests/blockchain_snapshot_test.go index a8044ecb4d..fb4c09867c 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/tests/blockchain_snapshot_test.go @@ -17,7 +17,7 @@ // Tests that abnormal program termination (i.e.crash) and restart can recovery // the snapshot properly if the snapshot is enabled. -package core +package tests import ( "bytes" @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -57,7 +58,9 @@ type snapshotTestBasic struct { engine consensus.Engine } -func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) { +func (basic *snapshotTestBasic) prepare(t *testing.T) (*core.BlockChain, []*types.Block) { + t.Helper() + // Create a temporary persistent database datadir, err := ioutil.TempDir("", "") if err != nil { @@ -71,20 +74,22 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } // Initialize a fresh chain var ( - genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + genesis = (&core.Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) engine = ethash.NewFullFaker() gendb = rawdb.NewMemoryDatabase() // Snapshot is enabled, the first snapshot is created from the Genesis. // The snapshot memory allowance is 256MB, it means no snapshot flush // will happen during the block insertion. - cacheConfig = defaultCacheConfig + cacheConfig = core.DefaultCacheConfig ) - chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + + chain, err := core.NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } - blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, func(i int, b *BlockGen) {}) + + blocks, _ := core.GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, func(i int, b *core.BlockGen) {}) // Insert the blocks with configured settings. var breakpoints []uint64 @@ -93,27 +98,39 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } else { breakpoints = append(breakpoints, basic.commitBlock, basic.snapshotBlock) } + var startPoint uint64 + for _, point := range breakpoints { if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } + startPoint = point if basic.commitBlock > 0 && basic.commitBlock == point { - chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil) + err = chain.StateCache().TrieDB().Commit(blocks[point-1].Root(), true, nil) + if err != nil { + t.Fatal("on trieDB.Commit", err) + } } + if basic.snapshotBlock > 0 && basic.snapshotBlock == point { // Flushing the entire snap tree into the disk, the // relevant (a) snapshot root and (b) snapshot generator // will be persisted atomically. - chain.snaps.Cap(blocks[point-1].Root(), 0) - diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() + err = chain.Snaps().Cap(blocks[point-1].Root(), 0) + if err != nil { + t.Fatal("on Snaps.Cap", err) + } + + diskRoot, blockRoot := chain.Snaps().DiskRoot(), blocks[point-1].Root() if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) } } } + if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) } @@ -123,10 +140,13 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo basic.db = db basic.gendb = gendb basic.engine = engine + return chain, blocks } -func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks []*types.Block) { +func (basic *snapshotTestBasic) verify(t *testing.T, chain *core.BlockChain, blocks []*types.Block) { + t.Helper() + // Iterate over all the remaining blocks and ensure there are no gaps verifyNoGaps(t, chain, true, blocks) verifyCutoff(t, chain, true, blocks, basic.expCanonicalBlocks) @@ -134,9 +154,11 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [ if head := chain.CurrentHeader(); head.Number.Uint64() != basic.expHeadHeader { t.Errorf("Head header mismatch: have %d, want %d", head.Number, basic.expHeadHeader) } + if head := chain.CurrentFastBlock(); head.NumberU64() != basic.expHeadFastBlock { t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadFastBlock) } + if head := chain.CurrentBlock(); head.NumberU64() != basic.expHeadBlock { t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadBlock) } @@ -145,12 +167,12 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [ block := chain.GetBlockByNumber(basic.expSnapshotBottom) if block == nil { t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom) - } else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { - t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) + } else if !bytes.Equal(chain.Snaps().DiskRoot().Bytes(), block.Root().Bytes()) { + t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.Snaps().DiskRoot()) } // Check the snapshot, ensure it's integrated - if err := chain.snaps.Verify(block.Root()); err != nil { + if err := chain.Snaps().Verify(block.Root()); err != nil { t.Errorf("The disk layer is not integrated %v", err) } } @@ -223,10 +245,12 @@ func (snaptest *snapshotTest) test(t *testing.T) { // Restart the chain normally chain.Stop() - newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + + newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } + defer newchain.Stop() snaptest.verify(t, newchain, blocks) @@ -245,7 +269,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { chain, blocks := snaptest.prepare(t) // Pull the plug on the database, simulating a hard crash - db := chain.db + db := chain.DB() db.Close() // Start a new blockchain back up and see where the repair leads us @@ -259,13 +283,13 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { // the crash, we do restart twice here: one after the crash and one // after the normal stop. It's used to ensure the broken snapshot // can be detected all the time. - newchain, err := NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } newchain.Stop() - newchain, err = NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = core.NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -292,27 +316,31 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { // Insert blocks without enabling snapshot if gapping is required. chain.Stop() - gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, func(i int, b *BlockGen) {}) + + gappedBlocks, _ := core.GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, func(i int, b *core.BlockGen) {}) // Insert a few more blocks without enabling snapshot - var cacheConfig = &CacheConfig{ + var cacheConfig = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, } - newchain, err := NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + + newchain, err := core.NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } + newchain.InsertChain(gappedBlocks) newchain.Stop() // Restart the chain with enabling the snapshot - newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } + defer newchain.Stop() snaptest.verify(t, newchain, blocks) @@ -337,7 +365,7 @@ func (snaptest *setHeadSnapshotTest) test(t *testing.T) { chain.SetHead(snaptest.setHead) chain.Stop() - newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -368,11 +396,12 @@ func (snaptest *restartCrashSnapshotTest) test(t *testing.T) { // and state committed. chain.Stop() - newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } - newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {}) + + newBlocks, _ := core.GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *core.BlockGen) {}) newchain.InsertChain(newBlocks) // Commit the entire snapshot into the disk if requested. Note only @@ -385,7 +414,7 @@ func (snaptest *restartCrashSnapshotTest) test(t *testing.T) { // journal and latest state will be committed // Restart the chain after the crash - newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -414,38 +443,42 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { // and state committed. chain.Stop() - config := &CacheConfig{ + config := &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, } - newchain, err := NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + + newchain, err := core.NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } - newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {}) + + newBlocks, _ := core.GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *core.BlockGen) {}) newchain.InsertChain(newBlocks) newchain.Stop() // Restart the chain, the wiper should starts working - config = &CacheConfig{ + config = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 256, SnapshotWait: false, // Don't wait rebuild } - newchain, err = NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + + _, err = core.NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } // Simulate the blockchain crash. - newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } + snaptest.verify(t, newchain, blocks) } diff --git a/eth/backend.go b/eth/backend.go index f40e011f8d..03419cde45 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -183,7 +183,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { // END: Bor changes bcVersion := rawdb.ReadDatabaseVersion(chainDb) - var dbVer = "" + dbVer := "" if bcVersion != nil { dbVer = fmt.Sprintf("%d", *bcVersion) } @@ -471,8 +471,10 @@ func (s *Ethereum) StartMining(threads int) error { if threads == 0 { threads = -1 // Disable the miner from within } + th.SetThreads(threads) } + // If the miner was not running, initialize it if !s.IsMining() { // Propagate the initial price point to the transaction pool @@ -485,6 +487,7 @@ func (s *Ethereum) StartMining(threads int) error { eb, err := s.Etherbase() if err != nil { log.Error("Cannot start mining without etherbase", "err", err) + return fmt.Errorf("etherbase missing: %v", err) } @@ -499,20 +502,26 @@ func (s *Ethereum) StartMining(threads int) error { cli = c } } + if cli != nil { wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) if wallet == nil || err != nil { log.Error("Etherbase account unavailable locally", "err", err) + return fmt.Errorf("signer missing: %v", err) } + cli.Authorize(eb, wallet.SignData) } + if bor, ok := s.engine.(*bor.Bor); ok { wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) if wallet == nil || err != nil { log.Error("Etherbase account unavailable locally", "err", err) + return fmt.Errorf("signer missing: %v", err) } + bor.Authorize(eb, wallet.SignData) } } @@ -522,6 +531,7 @@ func (s *Ethereum) StartMining(threads int) error { go s.miner.Start(eb) } + return nil } @@ -575,6 +585,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol { if s.config.SnapshotCache > 0 { protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...) } + return protos } @@ -599,6 +610,7 @@ func (s *Ethereum) Start() error { } // Start the networking layer and the light server if requested s.handler.Start(maxPeers) + return nil } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 6ab43891f7..904fb2cbc2 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -29,6 +29,9 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/bor" + "github.com/ethereum/go-ethereum/consensus/bor/contract" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" @@ -237,7 +240,10 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, et // 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 != "" { - return bor.New(chainConfig, db, blockchainAPI, ethConfig.HeimdallURL, ethConfig.WithoutHeimdall) + genesisContractsClient := contract.NewGenesisContractsClient(chainConfig, chainConfig.Bor.ValidatorContract, chainConfig.Bor.StateReceiverContract, blockchainAPI) + spanner := span.NewChainSpanner(blockchainAPI, contract.ValidatorSet(), chainConfig, common.HexToAddress(chainConfig.Bor.ValidatorContract)) + + return bor.New(chainConfig, db, blockchainAPI, spanner, heimdall.NewHeimdallClient(ethConfig.HeimdallURL), genesisContractsClient) } else { switch config.PowMode { case ethash.ModeFake: diff --git a/eth/tracers/api_bor.go b/eth/tracers/api_bor.go index 7f5e2c34d6..6ab1a4290a 100644 --- a/eth/tracers/api_bor.go +++ b/eth/tracers/api_bor.go @@ -47,16 +47,19 @@ func (api *API) traceBorBlock(ctx context.Context, block *types.Block, config *T if err != nil { return nil, err } + res.Block = blockFields parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) if err != nil { return nil, err } + reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } + // TODO: discuss consequences of setting preferDisk false. statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) if err != nil { @@ -96,6 +99,7 @@ func (api *API) traceBorBlock(ctx context.Context, block *types.Block, config *T if len(execRes.Revert()) > 0 { returnVal = fmt.Sprintf("%x", execRes.Revert()) } + result := ðapi.ExecutionResult{ Gas: execRes.UsedGas, Failed: execRes.Failed(), @@ -106,12 +110,14 @@ func (api *API) traceBorBlock(ctx context.Context, block *types.Block, config *T Result: result, IntermediateHash: statedb.IntermediateRoot(deleteEmptyObjects), } + return res } for indx, tx := range txs { res.Transactions = append(res.Transactions, traceTxn(indx, tx)) } + return res, nil } @@ -139,5 +145,6 @@ func (api *API) TraceBorBlock(req *TraceBlockRequest) (*BlockTraceResult, error) if err != nil { return nil, err } + return api.traceBorBlock(ctx, block, req.Config) } diff --git a/go.mod b/go.mod index 5e3a8b81e9..852c895e3b 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ module github.com/ethereum/go-ethereum -go 1.16 +go 1.18 require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.6.0 github.com/aws/aws-sdk-go-v2 v1.2.0 github.com/aws/aws-sdk-go-v2/config v1.1.1 @@ -16,20 +15,15 @@ require ( github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.8.0 - github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48 github.com/edsrzf/mmap-go v1.0.0 github.com/fatih/color v1.7.0 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/go-critic/go-critic v0.6.3 // indirect - github.com/go-kit/kit v0.9.0 // indirect - github.com/go-logfmt/logfmt v0.5.0 // indirect - github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-stack/stack v1.8.0 - github.com/go-toolsmith/astcopy v1.0.1 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 + github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 github.com/golang/snappy v0.0.4 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa @@ -45,25 +39,19 @@ require ( github.com/imdario/mergo v0.3.11 github.com/influxdata/influxdb v1.8.3 github.com/influxdata/influxdb-client-go/v2 v2.4.0 - github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect github.com/jackpal/go-nat-pmp v1.0.2 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e github.com/julienschmidt/httprouter v1.3.0 github.com/karalabe/usb v0.0.2 - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-isatty v0.0.12 github.com/mitchellh/cli v1.1.2 github.com/mitchellh/go-grpc-net-conn v0.0.0-20200427190222-eb030e4876f0 github.com/mitchellh/go-homedir v1.1.0 - github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.5 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 - github.com/polyfloyd/go-errorlint v0.0.0-20220510153142-36539f2bdac3 // indirect github.com/prometheus/tsdb v0.7.1 - github.com/quasilyte/gogrep v0.0.0-20220429205452-5e2753ee08f9 // indirect - github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v1.7.0 github.com/ryanuber/columnize v2.1.2+incompatible @@ -71,24 +59,81 @@ require ( github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/xsleonard/go-merkle v1.1.0 go.opentelemetry.io/otel v1.2.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.2.0 go.opentelemetry.io/otel/sdk v1.2.0 - go.uber.org/goleak v1.1.12 // indirect + go.uber.org/goleak v1.1.12 golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 golang.org/x/text v0.3.7 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba golang.org/x/tools v0.1.10 - golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect google.golang.org/grpc v1.42.0 google.golang.org/protobuf v1.27.1 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 + pgregory.net/rapid v0.4.7 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 // indirect + github.com/Masterminds/goutils v1.1.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.1.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.1.1 // indirect + github.com/aws/smithy-go v1.1.0 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/cenkalti/backoff/v4 v4.1.1 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/deepmap/oapi-codegen v1.8.2 // indirect + github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect + github.com/go-kit/kit v0.9.0 // indirect + github.com/go-logfmt/logfmt v0.5.0 // indirect + github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.0.0 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/naoina/go-stringutil v0.1.0 // indirect + github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/posener/complete v1.1.1 // indirect + github.com/tklauser/go-sysconf v0.3.5 // indirect + github.com/tklauser/numcpus v0.2.2 // indirect + github.com/zclconf/go-cty v1.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.2.0 // indirect + go.opentelemetry.io/otel/trace v1.2.0 // indirect + go.opentelemetry.io/proto/otlp v0.10.0 // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect + golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index b5278c5112..374ab3066e 100644 --- a/go.sum +++ b/go.sum @@ -49,7 +49,6 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= @@ -82,14 +81,12 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2 github.com/btcsuite/btcd/btcec/v2 v2.1.2 h1:YoYoC9J0jwfukodSBMzZYUVQ8PTiYg4BnOWiJVzTmLs= github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0 h1:MSskdM4/xJYcFzy0altH/C/xHopifpWzHUi1JeVI34Q= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -162,8 +159,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= -github.com/go-critic/go-critic v0.6.3 h1:abibh5XYBTASawfTQ0rA7dVtQT+6KzpGqb/J+DxRDaw= -github.com/go-critic/go-critic v0.6.3/go.mod h1:c6b3ZP1MQ7o6lPR7Rv3lEf7pYQUmAcx8ABHgdZCQt/k= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -184,25 +179,6 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= -github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= -github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= -github.com/go-toolsmith/astcopy v1.0.1 h1:l09oBhAPyV74kLJ3ZO31iBU8htZGTwr9LTjuMCyL8go= -github.com/go-toolsmith/astcopy v1.0.1/go.mod h1:4TcEdbElGc9twQEYpVo/aieIXfHhiuLh4aLAck6dO7Y= -github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astequal v1.0.1/go.mod h1:4oGA3EZXTVItV/ipGiOx7NWkY5veFfcsOJVS2YxltLw= -github.com/go-toolsmith/astequal v1.0.2 h1:+XvaV8zNxua+9+Oa4AHmgmpo4RYAbwr/qjNppLfX2yM= -github.com/go-toolsmith/astequal v1.0.2/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= -github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= -github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= -github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= -github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5 h1:eD9POs68PHkwrx7hAB78z1cb6PfGq/jyWn3wJywsH1o= -github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5/go.mod h1:3NAwwmD4uY/yggRxoEjk/S00MIV3A+H7rrE3i87eYxM= -github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/typep v1.0.2 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYwvlk= -github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -216,6 +192,8 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4er github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -247,10 +225,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -429,8 +405,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v0.0.0-20220510153142-36539f2bdac3 h1:GhmKVnwiethXkJVYqq/kdcw3+u2TIuhPwmHhB+ehpIQ= -github.com/polyfloyd/go-errorlint v0.0.0-20220510153142-36539f2bdac3/go.mod h1:KZy4xxPJyy88/gldCe5OdW6OQRtNO3EZE7hXzmnebgA= github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -445,21 +419,6 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30= -github.com/quasilyte/go-ruleguard v0.3.16-0.20220213074421-6aa060fab41a h1:sWFavxtIctGrVs5SYZ5Ml1CvrDAs8Kf5kx2PI3C41dA= -github.com/quasilyte/go-ruleguard v0.3.16-0.20220213074421-6aa060fab41a/go.mod h1:VMX+OnnSw4LicdiEGtRSD/1X8kW7GuEscjYNr4cOIT4= -github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/dsl v0.3.16/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= -github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= -github.com/quasilyte/gogrep v0.0.0-20220120141003-628d8b3623b5/go.mod h1:wSEyW6O61xRV6zb6My3HxrQ5/8ke7NE2OayqCHa3xRM= -github.com/quasilyte/gogrep v0.0.0-20220429205452-5e2753ee08f9 h1:mEQjyqtrQ6eB7oW9Qm+5po33faQIbhWp3qfhxGYuHKc= -github.com/quasilyte/gogrep v0.0.0-20220429205452-5e2753ee08f9/go.mod h1:MsVMK2P94jAne/7vEaLCRmjmOL0aTGAkvVOVuT+UYww= -github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= @@ -488,7 +447,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -518,10 +476,8 @@ github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPyS github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xsleonard/go-merkle v1.1.0 h1:fHe1fuhJjGH22ZzVTAH0jqHLhTGhOq3wQjJN+8P0jQg= github.com/xsleonard/go-merkle v1.1.0/go.mod h1:cW4z+UZ/4f2n9IJgIiyDCdYguchoDyDAPmpuOWGxdGg= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= @@ -557,9 +513,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8= golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -571,10 +525,7 @@ golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxT golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299 h1:zQpM52jfKHG6II1ISZY1ZcpygvuSFZpLwfluuF89XOg= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171 h1:DZhP7zSquENyG3Yb6ZpGqNEtgE8dfXhcLcheIF9RQHY= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -585,6 +536,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -592,9 +544,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -615,7 +565,6 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -625,10 +574,7 @@ golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -642,7 +588,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -686,12 +631,6 @@ golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -737,18 +676,14 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -847,5 +782,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= +pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/cli/flagset/flagset.go b/internal/cli/flagset/flagset.go index e04f452c1e..b57fff9996 100644 --- a/internal/cli/flagset/flagset.go +++ b/internal/cli/flagset/flagset.go @@ -19,6 +19,7 @@ func NewFlagSet(name string) *Flagset { flags: []*FlagVar{}, set: flag.NewFlagSet(name, flag.ContinueOnError), } + return f } @@ -34,10 +35,12 @@ func (f *Flagset) addFlag(fl *FlagVar) { func (f *Flagset) Help() string { str := "Options:\n\n" + items := []string{} for _, item := range f.flags { items = append(items, fmt.Sprintf(" -%s\n %s", item.Name, item.Usage)) } + return str + strings.Join(items, "\n\n") } @@ -53,25 +56,30 @@ func (f *Flagset) MarkDown() string { groups[item.Group] = append(groups[item.Group], item) } - keys := make([]string, len(groups)) i := 0 + keys := make([]string, len(groups)) + for k := range groups { keys[i] = k i++ } + sort.Strings(keys) items := []string{} + for _, k := range keys { if k == "" { - items = append(items, fmt.Sprintf("## Options")) + items = append(items, "## Options") } else { items = append(items, fmt.Sprintf("### %s Options", k)) } + for _, item := range groups[k] { items = append(items, fmt.Sprintf("- ```%s```: %s", item.Name, item.Usage)) } } + return strings.Join(items, "\n\n") } @@ -162,6 +170,7 @@ func (b *BigIntFlag) String() string { if b.Value == nil { return "" } + return b.Value.String() } @@ -174,10 +183,12 @@ func (b *BigIntFlag) Set(value string) error { } else { num, ok = num.SetString(value, 10) } + if !ok { return fmt.Errorf("failed to set big int") } b.Value = num + return nil } @@ -201,11 +212,13 @@ func (i *SliceStringFlag) String() string { if i.Value == nil { return "" } + return strings.Join(*i.Value, ",") } func (i *SliceStringFlag) Set(value string) error { *i.Value = append(*i.Value, strings.Split(value, ",")...) + return nil } @@ -246,10 +259,12 @@ func (m *MapStringFlag) String() string { if m.Value == nil { return "" } + ls := []string{} for k, v := range *m.Value { ls = append(ls, k+"="+v) } + return strings.Join(ls, ",") } @@ -257,6 +272,7 @@ func (m *MapStringFlag) Set(value string) error { if m.Value == nil { m.Value = &map[string]string{} } + for _, t := range strings.Split(value, ",") { if t != "" { kv := strings.Split(t, "=") @@ -266,6 +282,7 @@ func (m *MapStringFlag) Set(value string) error { } } } + return nil } diff --git a/internal/cli/server/chains/chain.go b/internal/cli/server/chains/chain.go index 32f5159b38..d6717f5893 100644 --- a/internal/cli/server/chains/chain.go +++ b/internal/cli/server/chains/chain.go @@ -26,18 +26,23 @@ var chains = map[string]*Chain{ } func GetChain(name string) (*Chain, error) { - var chain *Chain - var err error + var ( + chain *Chain + err error + ) + if _, fileErr := os.Stat(name); fileErr == nil { if chain, err = ImportFromFile(name); err != nil { return nil, fmt.Errorf("error importing chain from file: %v", err) } + return chain, nil } else if errors.Is(fileErr, os.ErrNotExist) { var ok bool if chain, ok = chains[name]; !ok { return nil, fmt.Errorf("chain %s not found", name) } + return chain, nil } else { return nil, fileErr @@ -62,10 +67,12 @@ func importChain(content []byte) (*Chain, error) { if chain.Genesis == nil { log.Info("Try reading as legacy genesis") + var genesis core.Genesis if err := json.Unmarshal(content, &genesis); err != nil { return nil, err } + if genesis.Config != nil { chain.Genesis = &genesis chain.NetworkId = genesis.Config.ChainID.Uint64() diff --git a/internal/cli/server/chains/chain_test.go b/internal/cli/server/chains/chain_test.go index 8f57b7d220..b372a3c4b9 100644 --- a/internal/cli/server/chains/chain_test.go +++ b/internal/cli/server/chains/chain_test.go @@ -5,9 +5,12 @@ import ( ) func TestChain_ImportFromFile(t *testing.T) { + t.Parallel() + type args struct { filename string } + tests := []struct { name string args args @@ -34,8 +37,13 @@ func TestChain_ImportFromFile(t *testing.T) { wantErr: true, }, } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + _, err := ImportFromFile(tt.args.filename) if (err != nil) != tt.wantErr { t.Errorf("ImportFromFile() error = %v, wantErr %v", err, tt.wantErr) diff --git a/internal/cli/server/command.go b/internal/cli/server/command.go index 01ce44f5ee..a0b8a6d87c 100644 --- a/internal/cli/server/command.go +++ b/internal/cli/server/command.go @@ -33,6 +33,7 @@ func (c *Command) MarkDown() string { "The ```bor server``` command runs the Bor client.", c.Flags().MarkDown(), } + return strings.Join(items, "\n\n") } diff --git a/internal/cli/server/config.go b/internal/cli/server/config.go index 4c17a58fc8..137f6aed0f 100644 --- a/internal/cli/server/config.go +++ b/internal/cli/server/config.go @@ -58,7 +58,7 @@ type Config struct { // GcMode selects the garbage collection mode for the trie GcMode string `hcl:"gc-mode,optional"` - // NoSnapshot disbales the snapshot database mode + // NoSnapshot disables the snapshot database mode NoSnapshot bool `hcl:"no-snapshot,optional"` // Ethstats is the address of the ethstats server to send telemetry @@ -527,18 +527,22 @@ func (c *Config) fillBigInt() error { b := new(big.Int) var ok bool + if strings.HasPrefix(*x.str, "0x") { b, ok = b.SetString((*x.str)[2:], 16) } else { b, ok = b.SetString(*x.str, 10) } + if !ok { return fmt.Errorf("%s can't parse big int %s", x.path, *x.str) } + *x.str = "" *x.td = b } } + return nil } @@ -559,10 +563,12 @@ func (c *Config) fillTimeDurations() error { if err != nil { return fmt.Errorf("%s can't parse time duration %s", x.path, *x.str) } + *x.str = "" *x.td = d } } + return nil } @@ -574,6 +580,7 @@ func readConfigFile(path string) (*Config, error) { if err != nil { return nil, err } + return readLegacyConfig(data) } @@ -582,15 +589,19 @@ func readConfigFile(path string) (*Config, error) { Cache: &CacheConfig{}, Sealer: &SealerConfig{}, } + if err := hclsimple.DecodeFile(path, nil, config); err != nil { return nil, fmt.Errorf("failed to decode config file '%s': %v", path, err) } + if err := config.fillBigInt(); err != nil { return nil, err } + if err := config.fillTimeDurations(); err != nil { return nil, err } + return config, nil } @@ -599,6 +610,7 @@ func (c *Config) loadChain() error { if err != nil { return err } + c.chain = chain // preload some default values that depend on the chain file @@ -612,14 +624,17 @@ func (c *Config) loadChain() error { } else { c.Cache.Cache = 1024 } + return nil } +//nolint:gocognit func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (*ethconfig.Config, error) { dbHandles, err := makeDatabaseHandles() if err != nil { return nil, err } + n := ethconfig.Defaults // only update for non-developer mode as we don't yet @@ -663,6 +678,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* if !common.IsHexAddress(etherbase) { return nil, fmt.Errorf("etherbase is not an address: %s", etherbase) } + n.Miner.Etherbase = common.HexToAddress(etherbase) } } @@ -672,17 +688,24 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() { return nil, fmt.Errorf("account unlock with HTTP access is forbidden") } + ks := accountManager.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + passwords, err := MakePasswordListFromFile(c.Accounts.PasswordFile) if err != nil { return nil, err } + if len(passwords) < len(c.Accounts.Unlock) { return nil, fmt.Errorf("number of passwords provided (%v) is less than number of accounts (%v) to unlock", len(passwords), len(c.Accounts.Unlock)) } + for i, account := range c.Accounts.Unlock { - ks.Unlock(accounts.Account{Address: common.HexToAddress(account)}, passwords[i]) + err = ks.Unlock(accounts.Account{Address: common.HexToAddress(account)}, passwords[i]) + if err != nil { + return nil, fmt.Errorf("could not unlock an account %q", account) + } } } @@ -700,6 +723,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* passphrase string err error ) + // etherbase has been set above, configuring the miner address from command line flags. if n.Miner.Etherbase != (common.Address{}) { developer = accounts.Account{Address: n.Miner.Etherbase} @@ -714,6 +738,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* if err := ks.Unlock(developer, passphrase); err != nil { return nil, fmt.Errorf("failed to unlock developer account: %v", err) } + log.Info("Using developer account", "address", developer.Address) // Set the Etherbase @@ -753,10 +778,12 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* if err != nil { return nil, fmt.Errorf("invalid required block number %s: %v", k, err) } + var hash common.Hash if err = hash.UnmarshalText([]byte(v)); err != nil { return nil, fmt.Errorf("invalid required block hash %s: %v", v, err) } + n.PeerRequiredBlocks[number] = hash } } @@ -775,6 +802,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024) mem.Total = 2 * 1024 * 1024 * 1024 } + allowance := uint64(mem.Total / 1024 / 1024 / 3) if cache > allowance { log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance) @@ -804,6 +832,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* } else { log.Info("Global gas cap disabled") } + n.RPCTxFeeCap = c.JsonRPC.TxFeeCap // sync mode. It can either be "fast", "full" or "snap". We disable @@ -843,6 +872,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* } n.DatabaseHandles = dbHandles + return &n, nil } @@ -906,6 +936,7 @@ func (c *Config) buildNode() (*node.Config, error) { cfg.HTTPHost = c.JsonRPC.Http.Host cfg.HTTPPort = int(c.JsonRPC.Http.Port) } + if c.JsonRPC.Ws.Enabled { cfg.WSHost = c.JsonRPC.Ws.Host cfg.WSPort = int(c.JsonRPC.Ws.Port) @@ -916,6 +947,7 @@ func (c *Config) buildNode() (*node.Config, error) { if err != nil { return nil, fmt.Errorf("wrong 'nat' flag: %v", err) } + cfg.P2P.NAT = natif // only check for non-developer modes @@ -926,21 +958,27 @@ func (c *Config) buildNode() (*node.Config, error) { if len(bootnodes) == 0 { bootnodes = c.chain.Bootnodes } + if cfg.P2P.BootstrapNodes, err = parseBootnodes(bootnodes); err != nil { return nil, err } + if cfg.P2P.BootstrapNodesV5, err = parseBootnodes(c.P2P.Discovery.BootnodesV5); err != nil { return nil, err } + if cfg.P2P.StaticNodes, err = parseBootnodes(c.P2P.Discovery.StaticNodes); err != nil { return nil, err } + if len(cfg.P2P.StaticNodes) == 0 { cfg.P2P.StaticNodes = cfg.StaticNodes() } + if cfg.P2P.TrustedNodes, err = parseBootnodes(c.P2P.Discovery.TrustedNodes); err != nil { return nil, err } + if len(cfg.P2P.TrustedNodes) == 0 { cfg.P2P.TrustedNodes = cfg.TrustedNodes() } @@ -951,6 +989,7 @@ func (c *Config) buildNode() (*node.Config, error) { cfg.P2P.MaxPeers = 0 cfg.P2P.NoDiscovery = true } + return cfg, nil } @@ -960,6 +999,7 @@ func (c *Config) Merge(cc ...*Config) error { return fmt.Errorf("failed to merge configurations: %v", err) } } + return nil } @@ -968,10 +1008,12 @@ func makeDatabaseHandles() (int, error) { if err != nil { return -1, err } + raised, err := fdlimit.Raise(uint64(limit)) if err != nil { return -1, err } + return int(raised / 2), nil } @@ -986,6 +1028,7 @@ func parseBootnodes(urls []string) ([]*enode.Node, error) { dst = append(dst, node) } } + return dst, nil } @@ -996,6 +1039,7 @@ func defaultDataDir() string { // we cannot guess a stable location return "" } + switch runtime.GOOS { case "darwin": return filepath.Join(home, "Library", "Bor") @@ -1005,6 +1049,7 @@ func defaultDataDir() string { // Windows XP and below don't have LocalAppData. panic("environment variable LocalAppData is undefined") } + return filepath.Join(appdata, "Bor") default: return filepath.Join(home, ".bor") @@ -1016,6 +1061,7 @@ func Hostname() string { if err != nil { return "bor" } + return hostname } @@ -1024,10 +1070,13 @@ func MakePasswordListFromFile(path string) ([]string, error) { if err != nil { return nil, fmt.Errorf("Failed to read password file: %v", err) } + lines := strings.Split(string(text), "\n") + // Sanitise DOS line endings. for i := range lines { lines[i] = strings.TrimRight(lines[i], "\r") } + return lines, nil } diff --git a/internal/cli/server/config_test.go b/internal/cli/server/config_test.go index 7368478123..94c4496c03 100644 --- a/internal/cli/server/config_test.go +++ b/internal/cli/server/config_test.go @@ -52,6 +52,7 @@ func TestConfigMerge(t *testing.T) { }, }, } + expected := &Config{ Chain: "1", NoSnapshot: false, @@ -69,11 +70,14 @@ func TestConfigMerge(t *testing.T) { }, }, } + assert.NoError(t, c0.Merge(c1)) assert.Equal(t, c0, expected) } func TestDefaultDatatypeOverride(t *testing.T) { + t.Parallel() + // This test is specific to `maxpeers` flag (for now) to check // if default datatype value (0 in case of uint64) is overridden. c0 := &Config{ @@ -81,16 +85,19 @@ func TestDefaultDatatypeOverride(t *testing.T) { MaxPeers: 30, }, } + c1 := &Config{ P2P: &P2PConfig{ MaxPeers: 0, }, } + expected := &Config{ P2P: &P2PConfig{ MaxPeers: 0, }, } + assert.NoError(t, c0.Merge(c1)) assert.Equal(t, c0, expected) } @@ -99,6 +106,7 @@ func TestConfigLoadFile(t *testing.T) { readFile := func(path string) { config, err := readConfigFile(path) assert.NoError(t, err) + assert.Equal(t, config, &Config{ DataDir: "./data", RequiredBlocks: map[string]string{ @@ -122,6 +130,7 @@ func TestConfigLoadFile(t *testing.T) { t.Run("hcl", func(t *testing.T) { readFile("./testdata/simple.hcl") }) + // read file in json format t.Run("json", func(t *testing.T) { readFile("./testdata/simple.json") @@ -152,7 +161,11 @@ func TestConfigBootnodesDefault(t *testing.T) { } func TestMakePasswordListFromFile(t *testing.T) { + t.Parallel() + t.Run("ReadPasswordFile", func(t *testing.T) { + t.Parallel() + result, _ := MakePasswordListFromFile("./testdata/password.txt") assert.Equal(t, []string{"test1", "test2"}, result) }) diff --git a/internal/cli/server/helper.go b/internal/cli/server/helper.go index c6831382b3..3a232d3185 100644 --- a/internal/cli/server/helper.go +++ b/internal/cli/server/helper.go @@ -17,10 +17,13 @@ func findAvailablePort(from int32, count int32) (int32, error) { if count == maxPortCheck { return 0, fmt.Errorf("no available port found") } + port := atomic.AddInt32(&from, 1) addr := fmt.Sprintf("localhost:%d", port) - lis, err := net.Listen("tcp", addr) + count++ + + lis, err := net.Listen("tcp", addr) if err == nil { lis.Close() return port, nil @@ -36,8 +39,13 @@ func CreateMockServer(config *Config) (*Server, error) { // find available port for grpc server rand.Seed(time.Now().UnixNano()) - var from int32 = 60000 // the min port to start checking from - var to int32 = 61000 // the max port to start checking from + + var ( + from int32 = 60000 // the min port to start checking from + to int32 = 61000 // the max port to start checking from + ) + + //nolint: gosec port, err := findAvailablePort(rand.Int31n(to-from+1)+from, 0) if err != nil { return nil, err @@ -53,10 +61,13 @@ func CreateMockServer(config *Config) (*Server, error) { // find available port for http server from = 8545 to = 9545 + + //nolint: gosec port, err = findAvailablePort(rand.Int31n(to-from+1)+from, 0) if err != nil { return nil, err } + config.JsonRPC.Http.Port = uint64(port) // start the server diff --git a/internal/cli/server/server.go b/internal/cli/server/server.go index 3d0b55e094..7d5ec2c8f3 100644 --- a/internal/cli/server/server.go +++ b/internal/cli/server/server.go @@ -11,6 +11,16 @@ import ( "strings" "time" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + "google.golang.org/grpc" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/consensus/beacon" @@ -26,15 +36,6 @@ import ( "github.com/ethereum/go-ethereum/metrics/influxdb" "github.com/ethereum/go-ethereum/metrics/prometheus" "github.com/ethereum/go-ethereum/node" - "github.com/mattn/go-colorable" - "github.com/mattn/go-isatty" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" - "google.golang.org/grpc" ) type Server struct { @@ -71,6 +72,7 @@ func NewServer(config *Config) (*Server, error) { if err != nil { return nil, err } + stack, err := node.New(nodeCfg) if err != nil { return nil, err @@ -82,6 +84,7 @@ func NewServer(config *Config) (*Server, error) { // register backend to account manager with keystore for signing keydir := stack.KeyStoreDir() + n, p := keystore.StandardScryptN, keystore.StandardScryptP if config.Accounts.UseLightweightKDF { n, p = keystore.LightScryptN, keystore.LightScryptP @@ -94,6 +97,7 @@ func NewServer(config *Config) (*Server, error) { authorized := false // check if personal wallet endpoints are disabled or not + // nolint:nestif if !config.Accounts.DisableBorWallet { // add keystore globally to the node's account manager if personal wallet is enabled stack.AccountManager().AddBackend(keystore.NewKeyStore(keydir, n, p)) @@ -103,10 +107,12 @@ func NewServer(config *Config) (*Server, error) { if err != nil { return nil, err } + backend, err := eth.New(stack, ethCfg) if err != nil { return nil, err } + srv.backend = backend } else { // register the ethereum backend (with temporary created account manager) @@ -114,10 +120,12 @@ func NewServer(config *Config) (*Server, error) { if err != nil { return nil, err } + backend, err := eth.New(stack, ethCfg) if err != nil { return nil, err } + srv.backend = backend // authorize only if mining or in developer mode @@ -126,6 +134,7 @@ func NewServer(config *Config) (*Server, error) { eb, err := srv.backend.Etherbase() if err != nil { log.Error("Cannot start mining without etherbase", "err", err) + return nil, fmt.Errorf("etherbase missing: %v", err) } @@ -142,8 +151,10 @@ func NewServer(config *Config) (*Server, error) { wallet, err := accountManager.Find(accounts.Account{Address: eb}) if wallet == nil || err != nil { log.Error("Etherbase account unavailable locally", "err", err) + return nil, fmt.Errorf("signer missing: %v", err) } + cli.Authorize(eb, wallet.SignData) authorized = true } @@ -155,6 +166,7 @@ func NewServer(config *Config) (*Server, error) { log.Error("Etherbase account unavailable locally", "err", err) return nil, fmt.Errorf("signer missing: %v", err) } + bor.Authorize(eb, wallet.SignData) authorized = true } @@ -200,6 +212,7 @@ func NewServer(config *Config) (*Server, error) { if err := srv.node.Start(); err != nil { return nil, err } + return srv, nil } @@ -238,10 +251,12 @@ func (s *Server) setupMetrics(config *TelemetryConfig, serviceName string) error if v1Enabled { log.Info("Enabling metrics export to InfluxDB (v1)") + go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Database, cfg.Username, cfg.Password, "geth.", tags) } if v2Enabled { log.Info("Enabling metrics export to InfluxDB (v2)") + go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Token, cfg.Bucket, cfg.Organization, "geth.", tags) } } @@ -328,6 +343,7 @@ func (s *Server) setupGRPCServer(addr string) error { }() log.Info("GRPC Server started", "addr", addr) + return nil } @@ -338,16 +354,20 @@ func (s *Server) withLoggingUnaryInterceptor() grpc.ServerOption { func (s *Server) loggingServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { start := time.Now() h, err := handler(ctx, req) + log.Trace("Request", "method", info.FullMethod, "duration", time.Since(start), "error", err) + return h, err } func setupLogger(logLevel string) { output := io.Writer(os.Stderr) + usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" if usecolor { output = colorable.NewColorableStderr() } + ostream := log.StreamHandler(output, log.TerminalFormat(usecolor)) glogger := log.NewGlogHandler(ostream) @@ -358,6 +378,7 @@ func setupLogger(logLevel string) { } else { glogger.Verbosity(log.LvlInfo) } + log.Root().SetHandler(glogger) } diff --git a/internal/cli/server/server_test.go b/internal/cli/server/server_test.go index a0b6dee2e3..c88c656b10 100644 --- a/internal/cli/server/server_test.go +++ b/internal/cli/server/server_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/assert" ) @@ -18,9 +19,11 @@ func nextPort() uint64 { log.Info("Checking for new port", "current", initialPort) port := atomic.AddUint64(&initialPort, 1) addr := fmt.Sprintf("localhost:%d", port) + lis, err := net.Listen("tcp", addr) if err == nil { lis.Close() + return port } else { return nextPort() @@ -28,6 +31,8 @@ func nextPort() uint64 { } func TestServer_DeveloperMode(t *testing.T) { + t.Parallel() + // get the default config config := DefaultConfig() @@ -38,6 +43,7 @@ func TestServer_DeveloperMode(t *testing.T) { // start the mock server server, err := CreateMockServer(config) assert.NoError(t, err) + defer CloseMockServer(server) // record the initial block number diff --git a/internal/cli/server/service.go b/internal/cli/server/service.go index 304b61b7d0..de240d5461 100644 --- a/internal/cli/server/service.go +++ b/internal/cli/server/service.go @@ -8,6 +8,10 @@ import ( "reflect" "strings" + gproto "github.com/golang/protobuf/proto" //nolint:staticcheck,typecheck + "github.com/golang/protobuf/ptypes/empty" + grpc_net_conn "github.com/mitchellh/go-grpc-net-conn" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/tracers" @@ -16,9 +20,6 @@ import ( "github.com/ethereum/go-ethereum/internal/cli/server/proto" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" - gproto "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes/empty" - grpc_net_conn "github.com/mitchellh/go-grpc-net-conn" ) const chunkSize = 1024 * 1024 * 1024 @@ -45,6 +46,7 @@ func sendStreamDebugFile(stream proto.Bor_DebugPprofServer, headers map[string]s Request: &proto.DebugFileResponse_Input{}, Encode: grpc_net_conn.ChunkedEncoder(encoder, chunkSize), } + if _, err := conn.Write(data); err != nil { return err } @@ -61,11 +63,14 @@ func sendStreamDebugFile(stream proto.Bor_DebugPprofServer, headers map[string]s } func (s *Server) DebugPprof(req *proto.DebugPprofRequest, stream proto.Bor_DebugPprofServer) error { - var payload []byte - var headers map[string]string - var err error + var ( + payload []byte + headers map[string]string + err error + ) ctx := context.Background() + switch req.Type { case proto.DebugPprofRequest_CPU: payload, headers, err = pprof.CPUProfile(ctx, int(req.Seconds)) @@ -74,6 +79,7 @@ func (s *Server) DebugPprof(req *proto.DebugPprofRequest, stream proto.Bor_Debug case proto.DebugPprofRequest_LOOKUP: payload, headers, err = pprof.Profile(req.Profile, 0, 0) } + if err != nil { return err } @@ -82,6 +88,7 @@ func (s *Server) DebugPprof(req *proto.DebugPprofRequest, stream proto.Bor_Debug if err := sendStreamDebugFile(stream, headers, payload); err != nil { return err } + return nil } @@ -192,6 +199,7 @@ func (s *Server) DebugBlock(req *proto.DebugBlockRequest, stream proto.Bor_Debug }, }, } + res, err := s.tracerAPI.TraceBorBlock(traceReq) if err != nil { return err @@ -202,6 +210,7 @@ func (s *Server) DebugBlock(req *proto.DebugBlockRequest, stream proto.Bor_Debug if err != nil { return err } + if err := sendStreamDebugFile(stream, map[string]string{}, data); err != nil { return err } diff --git a/miner/fake_miner.go b/miner/fake_miner.go new file mode 100644 index 0000000000..cbe0d9f54c --- /dev/null +++ b/miner/fake_miner.go @@ -0,0 +1,233 @@ +package miner + +import ( + "errors" + "math/big" + "testing" + "time" + + "github.com/golang/mock/gomock" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/bor" + "github.com/ethereum/go-ethereum/consensus/bor/api" + "github.com/ethereum/go-ethereum/consensus/bor/valset" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/tests/bor/mocks" + "github.com/ethereum/go-ethereum/trie" +) + +type DefaultBorMiner struct { + Miner *Miner + Mux *event.TypeMux //nolint:staticcheck + Cleanup func(skipMiner bool) + + Ctrl *gomock.Controller + EthAPIMock api.Caller + HeimdallClientMock bor.IHeimdallClient + ContractMock bor.GenesisContract +} + +func NewBorDefaultMiner(t *testing.T) *DefaultBorMiner { + t.Helper() + + ctrl := gomock.NewController(t) + + ethAPI := api.NewMockCaller(ctrl) + ethAPI.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + + spanner := bor.NewMockSpanner(ctrl) + spanner.EXPECT().GetCurrentValidators(gomock.Any(), gomock.Any()).Return([]*valset.Validator{ + { + ID: 0, + Address: common.Address{0x1}, + VotingPower: 100, + ProposerPriority: 0, + }, + }, nil).AnyTimes() + + heimdallClient := mocks.NewMockIHeimdallClient(ctrl) + heimdallClient.EXPECT().Close().Times(1) + + genesisContracts := bor.NewMockGenesisContract(ctrl) + + miner, mux, cleanup := createBorMiner(t, ethAPI, spanner, heimdallClient, genesisContracts) + + return &DefaultBorMiner{ + Miner: miner, + Mux: mux, + Cleanup: cleanup, + Ctrl: ctrl, + EthAPIMock: ethAPI, + HeimdallClientMock: heimdallClient, + ContractMock: genesisContracts, + } +} + +//nolint:staticcheck +func createBorMiner(t *testing.T, ethAPIMock api.Caller, spanner bor.Spanner, heimdallClientMock bor.IHeimdallClient, contractMock bor.GenesisContract) (*Miner, *event.TypeMux, func(skipMiner bool)) { + t.Helper() + + // Create Ethash config + chainDB, _, chainConfig := NewDBForFakes(t) + + engine := NewFakeBor(t, chainDB, chainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock) + + // Create Ethereum backend + bc, err := core.NewBlockChain(chainDB, nil, chainConfig, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("can't create new chain %v", err) + } + + statedb, _ := state.New(common.Hash{}, state.NewDatabase(chainDB), nil) + blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} + + pool := core.NewTxPool(testTxPoolConfig, chainConfig, blockchain) + backend := NewMockBackend(bc, pool) + + // Create event Mux + mux := new(event.TypeMux) + + config := Config{ + Etherbase: common.HexToAddress("123456789"), + } + + // Create Miner + miner := New(backend, &config, chainConfig, mux, engine, nil) + + cleanup := func(skipMiner bool) { + bc.Stop() + engine.Close() + pool.Stop() + + if !skipMiner { + miner.Close() + } + } + + return miner, mux, cleanup +} + +func NewDBForFakes(t *testing.T) (ethdb.Database, *core.Genesis, *params.ChainConfig) { + t.Helper() + + memdb := memorydb.New() + chainDB := rawdb.NewDatabase(memdb) + genesis := core.DeveloperGenesisBlock(2, 11_500_000, common.HexToAddress("12345")) + + chainConfig, _, err := core.SetupGenesisBlock(chainDB, genesis) + if err != nil { + t.Fatalf("can't create new chain config: %v", err) + } + + chainConfig.Bor.Period = map[string]uint64{ + "0": 1, + } + + return chainDB, genesis, chainConfig +} + +func NewFakeBor(t *testing.T, chainDB ethdb.Database, chainConfig *params.ChainConfig, ethAPIMock api.Caller, spanner bor.Spanner, heimdallClientMock bor.IHeimdallClient, contractMock bor.GenesisContract) consensus.Engine { + t.Helper() + + if chainConfig.Bor == nil { + chainConfig.Bor = params.BorUnittestChainConfig.Bor + } + + return bor.New(chainConfig, chainDB, ethAPIMock, spanner, heimdallClientMock, contractMock) +} + +type mockBackend struct { + bc *core.BlockChain + txPool *core.TxPool +} + +func NewMockBackend(bc *core.BlockChain, txPool *core.TxPool) *mockBackend { + return &mockBackend{ + bc: bc, + txPool: txPool, + } +} + +func (m *mockBackend) BlockChain() *core.BlockChain { + return m.bc +} + +func (m *mockBackend) TxPool() *core.TxPool { + return m.txPool +} + +func (m *mockBackend) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) { + return nil, errors.New("not supported") +} + +type testBlockChain struct { + statedb *state.StateDB + gasLimit uint64 + chainHeadFeed *event.Feed +} + +func (bc *testBlockChain) CurrentBlock() *types.Block { + return types.NewBlock(&types.Header{ + GasLimit: bc.gasLimit, + }, nil, nil, nil, trie.NewStackTrie(nil)) +} + +func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { + return bc.CurrentBlock() +} + +func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { + return bc.statedb, nil +} + +func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return bc.chainHeadFeed.Subscribe(ch) +} + +var ( + // Test chain configurations + testTxPoolConfig core.TxPoolConfig + ethashChainConfig *params.ChainConfig + cliqueChainConfig *params.ChainConfig + + // Test accounts + testBankKey, _ = crypto.GenerateKey() + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) + testBankFunds = big.NewInt(1000000000000000000) + + testUserKey, _ = crypto.GenerateKey() + testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) + + // Test transactions + pendingTxs []*types.Transaction + newTxs []*types.Transaction + + testConfig = &Config{ + Recommit: time.Second, + GasCeil: params.GenesisGasLimit, + } +) + +func init() { + testTxPoolConfig = core.DefaultTxPoolConfig + testTxPoolConfig.Journal = "" + ethashChainConfig = new(params.ChainConfig) + *ethashChainConfig = *params.TestChainConfig + cliqueChainConfig = new(params.ChainConfig) + *cliqueChainConfig = *params.TestChainConfig + cliqueChainConfig.Clique = ¶ms.CliqueConfig{ + Period: 10, + Epoch: 30000, + } +} diff --git a/miner/miner_test.go b/miner/miner_test.go index 03c79cbbd5..76f61fd0d4 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -18,80 +18,32 @@ package miner import ( - "errors" "testing" "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/clique" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/ethdb/memorydb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/trie" ) -type mockBackend struct { - bc *core.BlockChain - txPool *core.TxPool -} - -func NewMockBackend(bc *core.BlockChain, txPool *core.TxPool) *mockBackend { - return &mockBackend{ - bc: bc, - txPool: txPool, - } -} - -func (m *mockBackend) BlockChain() *core.BlockChain { - return m.bc -} - -func (m *mockBackend) TxPool() *core.TxPool { - return m.txPool -} - -func (m *mockBackend) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) { - return nil, errors.New("not supported") -} - -type testBlockChain struct { - statedb *state.StateDB - gasLimit uint64 - chainHeadFeed *event.Feed -} - -func (bc *testBlockChain) CurrentBlock() *types.Block { - return types.NewBlock(&types.Header{ - GasLimit: bc.gasLimit, - }, nil, nil, nil, trie.NewStackTrie(nil)) -} - -func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { - return bc.CurrentBlock() -} - -func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { - return bc.statedb, nil -} - -func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return bc.chainHeadFeed.Subscribe(ch) -} - func TestMiner(t *testing.T) { - miner, mux, cleanup := createMiner(t) - defer cleanup(false) + t.Parallel() + + minerBor := NewBorDefaultMiner(t) + defer func() { + minerBor.Cleanup(false) + minerBor.Ctrl.Finish() + }() + + miner := minerBor.Miner + mux := minerBor.Mux + miner.Start(common.HexToAddress("0x12345")) waitForMiningState(t, miner, true) + // Start the downloader mux.Post(downloader.StartEvent{}) waitForMiningState(t, miner, false) + // Stop the downloader and wait for the update loop to run mux.Post(downloader.DoneEvent{}) waitForMiningState(t, miner, true) @@ -113,10 +65,20 @@ func TestMiner(t *testing.T) { // An initial FailedEvent should allow mining to stop on a subsequent // downloader StartEvent. func TestMinerDownloaderFirstFails(t *testing.T) { - miner, mux, cleanup := createMiner(t) - defer cleanup(false) + t.Parallel() + + minerBor := NewBorDefaultMiner(t) + defer func() { + minerBor.Cleanup(false) + minerBor.Ctrl.Finish() + }() + + miner := minerBor.Miner + mux := minerBor.Mux + miner.Start(common.HexToAddress("0x12345")) waitForMiningState(t, miner, true) + // Start the downloader mux.Post(downloader.StartEvent{}) waitForMiningState(t, miner, false) @@ -145,10 +107,20 @@ func TestMinerDownloaderFirstFails(t *testing.T) { } func TestMinerStartStopAfterDownloaderEvents(t *testing.T) { - miner, mux, cleanup := createMiner(t) - defer cleanup(false) + t.Parallel() + + minerBor := NewBorDefaultMiner(t) + defer func() { + minerBor.Cleanup(false) + minerBor.Ctrl.Finish() + }() + + miner := minerBor.Miner + mux := minerBor.Mux + miner.Start(common.HexToAddress("0x12345")) waitForMiningState(t, miner, true) + // Start the downloader mux.Post(downloader.StartEvent{}) waitForMiningState(t, miner, false) @@ -168,58 +140,104 @@ func TestMinerStartStopAfterDownloaderEvents(t *testing.T) { } func TestStartWhileDownload(t *testing.T) { - miner, mux, cleanup := createMiner(t) - defer cleanup(false) + t.Parallel() + + minerBor := NewBorDefaultMiner(t) + defer func() { + minerBor.Cleanup(false) + minerBor.Ctrl.Finish() + }() + + miner := minerBor.Miner + mux := minerBor.Mux + waitForMiningState(t, miner, false) miner.Start(common.HexToAddress("0x12345")) waitForMiningState(t, miner, true) + // Stop the downloader and wait for the update loop to run mux.Post(downloader.StartEvent{}) waitForMiningState(t, miner, false) + // Starting the miner after the downloader should not work miner.Start(common.HexToAddress("0x12345")) waitForMiningState(t, miner, false) } func TestStartStopMiner(t *testing.T) { - miner, _, cleanup := createMiner(t) - defer cleanup(false) + t.Parallel() + + minerBor := NewBorDefaultMiner(t) + defer func() { + minerBor.Cleanup(false) + minerBor.Ctrl.Finish() + }() + + miner := minerBor.Miner + waitForMiningState(t, miner, false) miner.Start(common.HexToAddress("0x12345")) - waitForMiningState(t, miner, true) - miner.Stop() - waitForMiningState(t, miner, false) + waitForMiningState(t, miner, true) + + miner.Stop() + + waitForMiningState(t, miner, false) } func TestCloseMiner(t *testing.T) { - miner, _, cleanup := createMiner(t) - defer cleanup(true) + t.Parallel() + + minerBor := NewBorDefaultMiner(t) + defer func() { + minerBor.Cleanup(true) + minerBor.Ctrl.Finish() + }() + + miner := minerBor.Miner + waitForMiningState(t, miner, false) + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + // Terminate the miner and wait for the update loop to run miner.Close() + waitForMiningState(t, miner, false) } // TestMinerSetEtherbase checks that etherbase becomes set even if mining isn't // possible at the moment func TestMinerSetEtherbase(t *testing.T) { - miner, mux, cleanup := createMiner(t) - defer cleanup(false) + t.Parallel() + + minerBor := NewBorDefaultMiner(t) + defer func() { + minerBor.Cleanup(false) + minerBor.Ctrl.Finish() + }() + + miner := minerBor.Miner + mux := minerBor.Mux + // Start with a 'bad' mining address miner.Start(common.HexToAddress("0xdead")) waitForMiningState(t, miner, true) + // Start the downloader mux.Post(downloader.StartEvent{}) waitForMiningState(t, miner, false) + // Now user tries to configure proper mining address miner.Start(common.HexToAddress("0x1337")) + // Stop the downloader and wait for the update loop to run mux.Post(downloader.DoneEvent{}) waitForMiningState(t, miner, true) + // The miner should now be using the good address if got, exp := miner.coinbase, common.HexToAddress("0x1337"); got != exp { t.Fatalf("Wrong coinbase, got %x expected %x", got, exp) @@ -239,46 +257,6 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) { return } } + t.Fatalf("Mining() == %t, want %t", state, mining) } - -func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { - // Create Ethash config - config := Config{ - Etherbase: common.HexToAddress("123456789"), - } - // Create chainConfig - memdb := memorydb.New() - chainDB := rawdb.NewDatabase(memdb) - genesis := core.DeveloperGenesisBlock(15, 11_500_000, common.HexToAddress("12345")) - chainConfig, _, err := core.SetupGenesisBlock(chainDB, genesis) - if err != nil { - t.Fatalf("can't create new chain config: %v", err) - } - // Create consensus engine - engine := clique.New(chainConfig.Clique, chainDB) - // Create Ethereum backend - consensus.NewMerger(rawdb.NewMemoryDatabase()) - bc, err := core.NewBlockChain(chainDB, nil, chainConfig, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("can't create new chain %v", err) - } - statedb, _ := state.New(common.Hash{}, state.NewDatabase(chainDB), nil) - blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} - - pool := core.NewTxPool(testTxPoolConfig, chainConfig, blockchain) - backend := NewMockBackend(bc, pool) - // Create event Mux - mux := new(event.TypeMux) - // Create Miner - miner := New(backend, &config, chainConfig, mux, engine, nil) - cleanup := func(skipMiner bool) { - bc.Stop() - engine.Close() - pool.Stop() - if !skipMiner { - miner.Close() - } - } - return miner, mux, cleanup -} diff --git a/miner/unconfirmed_test.go b/miner/unconfirmed_test.go index dc83cb9265..70d3325506 100644 --- a/miner/unconfirmed_test.go +++ b/miner/unconfirmed_test.go @@ -44,6 +44,7 @@ func TestUnconfirmedInsertBounds(t *testing.T) { for i := 0; i < int(depth); i++ { pool.Insert(depth, [32]byte{byte(depth), byte(i)}) } + // Validate that no blocks below the depth allowance are left in pool.blocks.Do(func(block interface{}) { if block := block.(*unconfirmedBlock); block.index+uint64(limit) <= depth { @@ -64,23 +65,29 @@ func TestUnconfirmedShifts(t *testing.T) { for depth := start; depth < start+uint64(limit); depth++ { pool.Insert(depth, [32]byte{byte(depth)}) } + // Try to shift below the limit and ensure no blocks are dropped pool.Shift(start + uint64(limit) - 1) if n := pool.blocks.Len(); n != int(limit) { t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit) } + // Try to shift half the blocks out and verify remainder pool.Shift(start + uint64(limit) - 1 + uint64(limit/2)) if n := pool.blocks.Len(); n != int(limit)/2 { t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit/2) } + // Try to shift all the remaining blocks out and verify emptyness pool.Shift(start + 2*uint64(limit)) + if n := pool.blocks.Len(); n != 0 { t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0) } + // Try to shift out from the empty set and make sure it doesn't break pool.Shift(start + 3*uint64(limit)) + if n := pool.blocks.Len(); n != 0 { t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0) } diff --git a/miner/worker.go b/miner/worker.go index c3a5e1c9ba..14c5ccd1a3 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -25,6 +25,7 @@ import ( "time" mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -393,8 +394,10 @@ func recalcRecommit(minRecommit, prev time.Duration, target float64, inc bool) t prevF = float64(prev.Nanoseconds()) next float64 ) + if inc { next = prevF*(1-intervalAdjustRatio) + intervalAdjustRatio*(target+intervalAdjustBias) + max := float64(maxRecommitInterval.Nanoseconds()) if next > max { next = max @@ -406,6 +409,7 @@ func recalcRecommit(minRecommit, prev time.Duration, target float64, inc bool) t next = min } } + return time.Duration(int64(next)) } diff --git a/miner/worker_test.go b/miner/worker_test.go index dd029433b8..00739844f2 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -24,9 +24,14 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/bor" + "github.com/ethereum/go-ethereum/consensus/bor/api" + "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" @@ -38,6 +43,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/tests/bor/mocks" ) const ( @@ -49,43 +55,9 @@ const ( testGas = 144109 ) -var ( - // Test chain configurations - testTxPoolConfig core.TxPoolConfig - ethashChainConfig *params.ChainConfig - cliqueChainConfig *params.ChainConfig - - // Test accounts - testBankKey, _ = crypto.GenerateKey() - testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) - testBankFunds = big.NewInt(1000000000000000000) - - testUserKey, _ = crypto.GenerateKey() - testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) - - // Test transactions - pendingTxs []*types.Transaction - newTxs []*types.Transaction - - testConfig = &Config{ - Recommit: time.Second, - GasCeil: params.GenesisGasLimit, - } -) - func init() { - testTxPoolConfig = core.DefaultTxPoolConfig - testTxPoolConfig.Journal = "" - ethashChainConfig = new(params.ChainConfig) - *ethashChainConfig = *params.TestChainConfig - cliqueChainConfig = new(params.ChainConfig) - *cliqueChainConfig = *params.TestChainConfig - cliqueChainConfig.Clique = ¶ms.CliqueConfig{ - Period: 10, - Epoch: 30000, - } - signer := types.LatestSigner(params.TestChainConfig) + tx1 := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{ ChainID: params.TestChainConfig.ChainID, Nonce: 0, @@ -94,6 +66,7 @@ func init() { Gas: params.TxGas, GasPrice: big.NewInt(params.InitialBaseFee), }) + pendingTxs = append(pendingTxs, tx1) tx2 := types.MustSignNewTx(testBankKey, signer, &types.LegacyTx{ @@ -103,6 +76,7 @@ func init() { Gas: params.TxGas, GasPrice: big.NewInt(params.InitialBaseFee), }) + newTxs = append(newTxs, tx2) rand.Seed(time.Now().UnixNano()) @@ -125,6 +99,12 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine } switch e := engine.(type) { + case *bor.Bor: + gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) + copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes()) + e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), testBankKey) + }) case *clique.Clique: gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes()) @@ -135,6 +115,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine default: t.Fatalf("unexpected consensus engine type: %T", engine) } + genesis := gspec.MustCommit(db) chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil, nil) @@ -149,10 +130,12 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine t.Fatalf("failed to insert origin chain: %v", err) } } + parent := genesis if n > 0 { parent = chain.GetBlockByHash(chain.CurrentBlock().ParentHash()) } + blocks, _ := core.GenerateChain(chainConfig, parent, engine, db, 1, func(i int, gen *core.BlockGen) { gen.SetCoinbase(testUserAddress) }) @@ -174,28 +157,36 @@ func (b *testWorkerBackend) StateAtBlock(block *types.Block, reexec uint64, base func (b *testWorkerBackend) newRandomUncle() *types.Block { var parent *types.Block + cur := b.chain.CurrentBlock() + if cur.NumberU64() == 0 { parent = b.chain.Genesis() } else { parent = b.chain.GetBlockByHash(b.chain.CurrentBlock().ParentHash()) } + blocks, _ := core.GenerateChain(b.chain.Config(), parent, b.chain.Engine(), b.db, 1, func(i int, gen *core.BlockGen) { var addr = make([]byte, common.AddressLength) + rand.Read(addr) gen.SetCoinbase(common.BytesToAddress(addr)) }) + return blocks[0] } func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { var tx *types.Transaction + gasPrice := big.NewInt(10 * params.InitialBaseFee) + if creation { tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey) } else { tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey) } + return tx } @@ -204,39 +195,85 @@ func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consens backend.txPool.AddLocals(pendingTxs) w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false) w.setEtherbase(testBankAddress) + return w, backend } func TestGenerateBlockAndImportEthash(t *testing.T) { - testGenerateBlockAndImport(t, false) + t.Parallel() + + testGenerateBlockAndImport(t, false, false) } func TestGenerateBlockAndImportClique(t *testing.T) { - testGenerateBlockAndImport(t, true) + t.Parallel() + + testGenerateBlockAndImport(t, true, false) } -func testGenerateBlockAndImport(t *testing.T, isClique bool) { +func TestGenerateBlockAndImportBor(t *testing.T) { + t.Parallel() + + testGenerateBlockAndImport(t, false, true) +} + +//nolint:thelper +func testGenerateBlockAndImport(t *testing.T, isClique bool, isBor bool) { var ( engine consensus.Engine chainConfig *params.ChainConfig db = rawdb.NewMemoryDatabase() ) - if isClique { - chainConfig = params.AllCliqueProtocolChanges - chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} - engine = clique.New(chainConfig.Clique, db) + + if isBor { + chainConfig = params.BorUnittestChainConfig + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ethAPIMock := api.NewMockCaller(ctrl) + ethAPIMock.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + + spanner := bor.NewMockSpanner(ctrl) + spanner.EXPECT().GetCurrentValidators(gomock.Any(), gomock.Any()).Return([]*valset.Validator{ + { + ID: 0, + Address: testBankAddress, + VotingPower: 100, + ProposerPriority: 0, + }, + }, nil).AnyTimes() + + heimdallClientMock := mocks.NewMockIHeimdallClient(ctrl) + heimdallClientMock.EXPECT().Close().Times(1) + + contractMock := bor.NewMockGenesisContract(ctrl) + + db, _, _ = NewDBForFakes(t) + + engine = NewFakeBor(t, db, chainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock) } else { - chainConfig = params.AllEthashProtocolChanges - engine = ethash.NewFaker() + if isClique { + chainConfig = params.AllCliqueProtocolChanges + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine = clique.New(chainConfig.Clique, db) + } else { + chainConfig = params.AllEthashProtocolChanges + engine = ethash.NewFaker() + } } + defer engine.Close() + chainConfig.LondonBlock = big.NewInt(0) + w, b := newTestWorker(t, chainConfig, engine, db, 0) defer w.close() // This test chain imports the mined blocks. db2 := rawdb.NewMemoryDatabase() b.genesis.MustCommit(db2) + chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil) defer chain.Stop() @@ -287,20 +324,25 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens taskIndex int taskCh = make(chan struct{}, 2) ) + checkEqual := func(t *testing.T, task *task, index int) { // The first empty work without any txs included receiptLen, balance := 0, big.NewInt(0) + if index == 1 { // The second full work with 1 tx included receiptLen, balance = 1, big.NewInt(1000) } + if len(task.receipts) != receiptLen { t.Fatalf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen) } + if task.state.GetBalance(testUserAddress).Cmp(balance) != 0 { t.Fatalf("account balance mismatch: have %d, want %d", task.state.GetBalance(testUserAddress), balance) } } + w.newTaskHook = func(task *task) { if task.block.NumberU64() == 1 { checkEqual(t, task, taskIndex) @@ -308,11 +350,14 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens taskCh <- struct{}{} } } + w.skipSealHook = func(task *task) bool { return true } w.fullTaskHook = func() { time.Sleep(100 * time.Millisecond) } + w.start() // Start mining! + for i := 0; i < 2; i += 1 { select { case <-taskCh: @@ -344,16 +389,20 @@ func TestStreamUncleBlock(t *testing.T) { t.Errorf("uncle hash mismatch: have %s, want %s", have.Hex(), want.Hex()) } } + taskCh <- struct{}{} taskIndex += 1 } } + w.skipSealHook = func(task *task) bool { return true } + w.fullTaskHook = func() { time.Sleep(100 * time.Millisecond) } + w.start() for i := 0; i < 2; i += 1 { @@ -396,20 +445,25 @@ func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, en // one has 1 pending tx, the third one has 2 txs if taskIndex == 2 { receiptLen, balance := 2, big.NewInt(2000) + if len(task.receipts) != receiptLen { t.Errorf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen) } + if task.state.GetBalance(testUserAddress).Cmp(balance) != 0 { t.Errorf("account balance mismatch: have %d, want %d", task.state.GetBalance(testUserAddress), balance) } } + taskCh <- struct{}{} taskIndex += 1 } } + w.skipSealHook = func(task *task) bool { return true } + w.fullTaskHook = func() { time.Sleep(100 * time.Millisecond) } @@ -423,6 +477,7 @@ func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, en t.Error("new task timeout") } } + b.txPool.AddLocals(newTxs) time.Sleep(time.Second) @@ -459,6 +514,7 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co index = 0 start uint32 ) + w.resubmitHook = func(minInterval time.Duration, recommitInterval time.Duration) { // Short circuit if interval checking hasn't started. if atomic.LoadUint32(&start) == 0 { @@ -489,16 +545,19 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co if recommitInterval != wantRecommitInterval { t.Errorf("resubmit interval mismatch: have %v, want %v", recommitInterval, wantRecommitInterval) } + result = append(result, float64(recommitInterval.Nanoseconds())) index += 1 progress <- struct{}{} } + w.start() time.Sleep(time.Second) // Ensure two tasks have been summitted due to start opt atomic.StoreUint32(&start, 1) w.setRecommitInterval(3 * time.Second) + select { case <-progress: case <-time.NewTimer(time.Second).C: @@ -506,6 +565,7 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co } w.resubmitAdjustCh <- &intervalAdjust{inc: true, ratio: 0.8} + select { case <-progress: case <-time.NewTimer(time.Second).C: @@ -513,6 +573,7 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co } w.resubmitAdjustCh <- &intervalAdjust{inc: false} + select { case <-progress: case <-time.NewTimer(time.Second).C: @@ -520,6 +581,7 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co } w.setRecommitInterval(500 * time.Millisecond) + select { case <-progress: case <-time.NewTimer(time.Second).C: @@ -554,9 +616,11 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co w.skipSealHook = func(task *task) bool { return true } + w.fullTaskHook = func() { time.Sleep(100 * time.Millisecond) } + timestamp := uint64(time.Now().Unix()) assertBlock := func(block *types.Block, number uint64, coinbase common.Address, random common.Hash) { if block.Time() != timestamp { @@ -564,14 +628,17 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co // is even smaller than parent block's. It's OK. t.Logf("Invalid timestamp, want %d, get %d", timestamp, block.Time()) } + if len(block.Uncles()) != 0 { t.Error("Unexpected uncle block") } + _, isClique := engine.(*clique.Clique) if !isClique { if len(block.Extra()) != 0 { t.Error("Unexpected extra field") } + if block.Coinbase() != coinbase { t.Errorf("Unexpected coinbase got %x want %x", block.Coinbase(), coinbase) } @@ -580,18 +647,22 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co t.Error("Unexpected coinbase") } } + if !isClique { if block.MixDigest() != random { t.Error("Unexpected mix digest") } } + if block.Nonce() != 0 { t.Error("Unexpected block nonce") } + if block.NumberU64() != number { t.Errorf("Mismatched block number, want %d got %d", number, block.NumberU64()) } } + var cases = []struct { parent common.Hash coinbase common.Address @@ -639,6 +710,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co // This API should work even when the automatic sealing is not enabled for _, c := range cases { block, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random) + if c.expectErr { if err == nil { t.Error("Expect error but get nil") @@ -647,12 +719,14 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co if err != nil { t.Errorf("Unexpected error %v", err) } + assertBlock(block, c.expectNumber, c.coinbase, c.random) } } // This API should work even when the automatic sealing is enabled w.start() + for _, c := range cases { block, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random) if c.expectErr { @@ -663,6 +737,7 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co if err != nil { t.Errorf("Unexpected error %v", err) } + assertBlock(block, c.expectNumber, c.coinbase, c.random) } } diff --git a/params/config.go b/params/config.go index 47e22f986c..980250a1ec 100644 --- a/params/config.go +++ b/params/config.go @@ -23,8 +23,9 @@ import ( "sort" "strconv" - "github.com/ethereum/go-ethereum/common" "golang.org/x/crypto/sha3" + + "github.com/ethereum/go-ethereum/common" ) // Genesis hashes to enforce below configs on. @@ -289,6 +290,38 @@ var ( }, }, } + BorUnittestChainConfig = &ChainConfig{ + ChainID: big.NewInt(80001), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Hash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + Bor: &BorConfig{ + Period: map[string]uint64{ + "0": 1, + }, + ProducerDelay: 3, + Sprint: 32, + BackupMultiplier: map[string]uint64{ + "0": 2, + }, + ValidatorContract: "0x0000000000000000000000000000000000001000", + StateReceiverContract: "0x0000000000000000000000000000000000001001", + BurntContract: map[string]string{ + "0": "0x00000000000000000000000000000000000000000", + }, + }, + } // MumbaiChainConfig contains the chain parameters to run a node on the Mumbai test network. MumbaiChainConfig = &ChainConfig{ @@ -550,7 +583,9 @@ func (c *BorConfig) calculateBorConfigHelper(field map[string]uint64, number uin for k := range field { keys = append(keys, k) } + sort.Strings(keys) + for i := 0; i < len(keys)-1; i++ { valUint, _ := strconv.ParseUint(keys[i], 10, 64) valUintNext, _ := strconv.ParseUint(keys[i+1], 10, 64) @@ -558,6 +593,7 @@ func (c *BorConfig) calculateBorConfigHelper(field map[string]uint64, number uin return field[keys[i]] } } + return field[keys[len(keys)-1]] } diff --git a/tests/bor/bor_test.go b/tests/bor/bor_test.go index 3bda2ee3f4..434ad805e1 100644 --- a/tests/bor/bor_test.go +++ b/tests/bor/bor_test.go @@ -5,14 +5,17 @@ package bor import ( "encoding/hex" - "encoding/json" "io" "math/big" "testing" "time" + "github.com/golang/mock/gomock" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/bor" + "github.com/ethereum/go-ethereum/consensus/bor/clerk" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" @@ -22,12 +25,12 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests/bor/mocks" + "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "golang.org/x/crypto/sha3" ) -var ( +const ( spanPath = "bor/span/1" clerkPath = "clerk/event-record/list" clerkQueryParams = "from-time=%d&to-time=%d&page=%d&limit=50" @@ -38,7 +41,10 @@ func TestInsertingSpanSizeBlocks(t *testing.T) { chain := init.ethereum.BlockChain() engine := init.ethereum.Engine() _bor := engine.(*bor.Bor) - h, heimdallSpan := getMockedHeimdallClient(t) + + h, heimdallSpan, ctrl := getMockedHeimdallClient(t) + defer ctrl.Finish() + _bor.SetHeimdallClient(h) db := init.ethereum.ChainDb() @@ -51,7 +57,6 @@ func TestInsertingSpanSizeBlocks(t *testing.T) { insertNewBlock(t, chain, block) } - assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, "")) validators, err := _bor.GetCurrentValidators(block.Hash(), spanSize) // check validator set at the first block of new span if err != nil { t.Fatalf("%s", err) @@ -82,8 +87,12 @@ func TestFetchStateSyncEvents(t *testing.T) { // B. Before inserting 1st block of the next sprint, mock heimdall deps // B.1 Mock /bor/span/1 res, _ := loadSpanFromFile(t) - h := &mocks.IHeimdallClient{} - h.On("FetchWithRetry", spanPath, "").Return(res, nil) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + h := mocks.NewMockIHeimdallClient(ctrl) + h.EXPECT().Span(uint64(1)).Return(&res.Result, nil).AnyTimes() // B.2 Mock State Sync events fromID := uint64(1) @@ -94,14 +103,12 @@ func TestFetchStateSyncEvents(t *testing.T) { sample := getSampleEventRecord(t) sample.Time = time.Unix(to-int64(eventCount+1), 0) // last event.Time will be just < to eventRecords := generateFakeStateSyncEvents(sample, eventCount) - h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil) + + h.EXPECT().StateSyncEvents(fromID, to).Return(eventRecords, nil).AnyTimes() _bor.SetHeimdallClient(h) block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) insertNewBlock(t, chain, block) - - assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, "")) - assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to)) } func TestFetchStateSyncEvents_2(t *testing.T) { @@ -112,8 +119,12 @@ func TestFetchStateSyncEvents_2(t *testing.T) { // Mock /bor/span/1 res, _ := loadSpanFromFile(t) - h := &mocks.IHeimdallClient{} - h.On("FetchWithRetry", spanPath, "").Return(res, nil) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + h := mocks.NewMockIHeimdallClient(ctrl) + h.EXPECT().Span(uint64(1)).Return(&res.Result, nil).AnyTimes() // Mock State Sync events // at # sprintSize, events are fetched for [fromID, (block-sprint).Time) @@ -123,7 +134,7 @@ func TestFetchStateSyncEvents_2(t *testing.T) { // First query will be from [id=1, (block-sprint).Time] // Insert 5 events in this time range - eventRecords := []*bor.EventRecordWithTime{ + eventRecords := []*clerk.EventRecordWithTime{ buildStateEvent(sample, 1, 3), // id = 1, time = 1 buildStateEvent(sample, 2, 1), // id = 2, time = 3 buildStateEvent(sample, 3, 2), // id = 3, time = 2 @@ -131,7 +142,8 @@ func TestFetchStateSyncEvents_2(t *testing.T) { buildStateEvent(sample, 4, 5), // id = 4, time = 5 buildStateEvent(sample, 6, 4), // id = 6, time = 4 } - h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil) + + h.EXPECT().StateSyncEvents(fromID, to).Return(eventRecords, nil).AnyTimes() _bor.SetHeimdallClient(h) // Insert blocks for 0th sprint @@ -141,25 +153,27 @@ func TestFetchStateSyncEvents_2(t *testing.T) { block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) insertNewBlock(t, chain, block) } - assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, "")) - assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to)) + lastStateID, _ := _bor.GenesisContractsClient.LastStateId(sprintSize) + // state 6 was not written assert.Equal(t, uint64(4), lastStateID.Uint64()) // fromID = uint64(5) to = int64(chain.GetHeaderByNumber(sprintSize).Time) - eventRecords = []*bor.EventRecordWithTime{ + + eventRecords = []*clerk.EventRecordWithTime{ buildStateEvent(sample, 5, 7), buildStateEvent(sample, 6, 4), } - h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil) + h.EXPECT().StateSyncEvents(fromID, to).Return(eventRecords, nil).AnyTimes() + for i := sprintSize + 1; i <= spanSize; i++ { block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) insertNewBlock(t, chain, block) } - assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to)) + lastStateID, _ = _bor.GenesisContractsClient.LastStateId(spanSize) assert.Equal(t, uint64(6), lastStateID.Uint64()) } @@ -169,7 +183,10 @@ func TestOutOfTurnSigning(t *testing.T) { chain := init.ethereum.BlockChain() engine := init.ethereum.Engine() _bor := engine.(*bor.Bor) - h, _ := getMockedHeimdallClient(t) + + h, _, ctrl := getMockedHeimdallClient(t) + defer ctrl.Finish() + _bor.SetHeimdallClient(h) db := init.ethereum.ChainDb() @@ -217,7 +234,10 @@ func TestSignerNotFound(t *testing.T) { chain := init.ethereum.BlockChain() engine := init.ethereum.Engine() _bor := engine.(*bor.Bor) - h, _ := getMockedHeimdallClient(t) + + h, _, ctrl := getMockedHeimdallClient(t) + defer ctrl.Finish() + _bor.SetHeimdallClient(h) db := init.ethereum.ChainDb() @@ -236,47 +256,46 @@ func TestSignerNotFound(t *testing.T) { bor.UnauthorizedSignerError{Number: 0, Signer: addr.Bytes()}) } -func getMockedHeimdallClient(t *testing.T) (*mocks.IHeimdallClient, *bor.HeimdallSpan) { - res, heimdallSpan := loadSpanFromFile(t) - h := &mocks.IHeimdallClient{} - h.On("FetchWithRetry", "bor/span/1", "").Return(res, nil) - h.On( - "FetchStateSyncEvents", - mock.AnythingOfType("uint64"), - mock.AnythingOfType("int64")).Return([]*bor.EventRecordWithTime{getSampleEventRecord(t)}, nil) - return h, heimdallSpan +func getMockedHeimdallClient(t *testing.T) (*mocks.MockIHeimdallClient, *span.HeimdallSpan, *gomock.Controller) { + ctrl := gomock.NewController(t) + h := mocks.NewMockIHeimdallClient(ctrl) + + _, heimdallSpan := loadSpanFromFile(t) + + h.EXPECT().Span(uint64(1)).Return(heimdallSpan, nil).AnyTimes() + + h.EXPECT().StateSyncEvents(gomock.Any(), gomock.Any()). + Return([]*clerk.EventRecordWithTime{getSampleEventRecord(t)}, nil).AnyTimes() + + return h, heimdallSpan, ctrl } -func generateFakeStateSyncEvents(sample *bor.EventRecordWithTime, count int) []*bor.EventRecordWithTime { - events := make([]*bor.EventRecordWithTime, count) +func generateFakeStateSyncEvents(sample *clerk.EventRecordWithTime, count int) []*clerk.EventRecordWithTime { + events := make([]*clerk.EventRecordWithTime, count) event := *sample event.ID = 1 - events[0] = &bor.EventRecordWithTime{} + events[0] = &clerk.EventRecordWithTime{} *events[0] = event for i := 1; i < count; i++ { event.ID = uint64(i) event.Time = event.Time.Add(1 * time.Second) - events[i] = &bor.EventRecordWithTime{} + events[i] = &clerk.EventRecordWithTime{} *events[i] = event } return events } -func buildStateEvent(sample *bor.EventRecordWithTime, id uint64, timeStamp int64) *bor.EventRecordWithTime { +func buildStateEvent(sample *clerk.EventRecordWithTime, id uint64, timeStamp int64) *clerk.EventRecordWithTime { event := *sample event.ID = id event.Time = time.Unix(timeStamp, 0) return &event } -func getSampleEventRecord(t *testing.T) *bor.EventRecordWithTime { - res := stateSyncEventsPayload(t) - var _eventRecords []*bor.EventRecordWithTime - if err := json.Unmarshal(res.Result, &_eventRecords); err != nil { - t.Fatalf("%s", err) - } - _eventRecords[0].Time = time.Unix(1, 0) - return _eventRecords[0] +func getSampleEventRecord(t *testing.T) *clerk.EventRecordWithTime { + eventRecords := stateSyncEventsPayload(t) + eventRecords.Result[0].Time = time.Unix(1, 0) + return eventRecords.Result[0] } // TestEIP1559Transition tests the following: diff --git a/tests/bor/helper.go b/tests/bor/helper.go index a1b6a19742..a8a3ae4ea6 100644 --- a/tests/bor/helper.go +++ b/tests/bor/helper.go @@ -11,6 +11,9 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/bor" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" + "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -46,17 +49,23 @@ type initializeData struct { } func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData { + t.Helper() + genesisData, err := ioutil.ReadFile("./testdata/genesis.json") if err != nil { t.Fatalf("%s", err) } + gen := &core.Genesis{} + if err := json.Unmarshal(genesisData, gen); err != nil { t.Fatalf("%s", err) } + ethConf := ð.Config{ Genesis: gen, } + ethConf.Genesis.MustCommit(db) ethereum := utils.CreateBorEthereum(ethConf) @@ -65,6 +74,7 @@ func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData { } ethConf.Genesis.MustCommit(ethereum.ChainDb()) + return &initializeData{ genesis: gen, ethereum: ethereum, @@ -72,12 +82,16 @@ func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData { } func insertNewBlock(t *testing.T, chain *core.BlockChain, block *types.Block) { + t.Helper() + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { t.Fatalf("%s", err) } } func buildNextBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, block *types.Block, signer []byte, borConfig *params.BorConfig) *types.Block { + t.Helper() + header := block.Header() header.Number.Add(header.Number, big.NewInt(1)) number := header.Number.Uint64() @@ -90,11 +104,12 @@ func buildNextBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, block * header.Time += bor.CalcProducerDelay(header.Number.Uint64(), 0, borConfig) header.Extra = make([]byte, 32+65) // vanity + extraSeal - currentValidators := []*bor.Validator{bor.NewValidator(addr, 10)} + currentValidators := []*valset.Validator{valset.NewValidator(addr, 10)} isSpanEnd := (number+1)%spanSize == 0 isSpanStart := number%spanSize == 0 isSprintEnd := (header.Number.Uint64()+1)%sprintSize == 0 + if isSpanEnd { _, heimdallSpan := loadSpanFromFile(t) // this is to stash the validator bytes in the header @@ -102,18 +117,23 @@ func buildNextBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, block * } else if isSpanStart { header.Difficulty = new(big.Int).SetInt64(3) } + if isSprintEnd { - sort.Sort(bor.ValidatorsByAddress(currentValidators)) + sort.Sort(valset.ValidatorsByAddress(currentValidators)) + validatorBytes := make([]byte, len(currentValidators)*validatorHeaderBytesLength) header.Extra = make([]byte, 32+len(validatorBytes)+65) // vanity + validatorBytes + extraSeal + for i, val := range currentValidators { copy(validatorBytes[i*validatorHeaderBytesLength:], val.HeaderBytes()) } + copy(header.Extra[32:], validatorBytes) } if chain.Config().IsLondon(header.Number) { header.BaseFee = misc.CalcBaseFee(chain.Config(), block.Header()) + if !chain.Config().IsLondon(block.Number()) { parentGasLimit := block.GasLimit() * params.ElasticityMultiplier header.GasLimit = core.CalcGasLimit(parentGasLimit, parentGasLimit) @@ -124,58 +144,73 @@ func buildNextBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, block * if err != nil { t.Fatalf("%s", err) } + _, err = _bor.FinalizeAndAssemble(chain, header, state, nil, nil, nil) if err != nil { t.Fatalf("%s", err) } + sign(t, header, signer, borConfig) + return types.NewBlockWithHeader(header) } func sign(t *testing.T, header *types.Header, signer []byte, c *params.BorConfig) { + t.Helper() + sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header, c)), signer) if err != nil { t.Fatalf("%s", err) } + copy(header.Extra[len(header.Extra)-extraSeal:], sig) } -func stateSyncEventsPayload(t *testing.T) *bor.ResponseWithHeight { +//nolint:unused,deadcode +func stateSyncEventsPayload(t *testing.T) *heimdall.StateSyncEventsResponse { + t.Helper() + stateData, err := ioutil.ReadFile("./testdata/states.json") if err != nil { t.Fatalf("%s", err) } - res := &bor.ResponseWithHeight{} + + res := &heimdall.StateSyncEventsResponse{} if err := json.Unmarshal(stateData, res); err != nil { t.Fatalf("%s", err) } + return res } -func loadSpanFromFile(t *testing.T) (*bor.ResponseWithHeight, *bor.HeimdallSpan) { +//nolint:unused,deadcode +func loadSpanFromFile(t *testing.T) (*heimdall.SpanResponse, *span.HeimdallSpan) { + t.Helper() + spanData, err := ioutil.ReadFile("./testdata/span.json") if err != nil { t.Fatalf("%s", err) } - res := &bor.ResponseWithHeight{} + + res := &heimdall.SpanResponse{} + if err := json.Unmarshal(spanData, res); err != nil { t.Fatalf("%s", err) } - heimdallSpan := &bor.HeimdallSpan{} - if err := json.Unmarshal(res.Result, heimdallSpan); err != nil { - t.Fatalf("%s", err) - } - return res, heimdallSpan + return res, &res.Result } func getSignerKey(number uint64) []byte { signerKey := privKey + isSpanStart := number%spanSize == 0 if isSpanStart { // validator set in the new span has changed signerKey = privKey2 } + _key, _ := hex.DecodeString(signerKey) + return _key } diff --git a/tests/bor/mocks/IHeimdallClient.go b/tests/bor/mocks/IHeimdallClient.go index ac89487d3b..4e4d7a3776 100644 --- a/tests/bor/mocks/IHeimdallClient.go +++ b/tests/bor/mocks/IHeimdallClient.go @@ -1,87 +1,78 @@ -// Code generated by mockery v2.10.0. DO NOT EDIT. +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ethereum/go-ethereum/consensus/bor (interfaces: IHeimdallClient) +// Package mocks is a generated GoMock package. package mocks import ( - bor "github.com/ethereum/go-ethereum/consensus/bor" - mock "github.com/stretchr/testify/mock" + reflect "reflect" + + clerk "github.com/ethereum/go-ethereum/consensus/bor/clerk" + span "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" + gomock "github.com/golang/mock/gomock" ) -// IHeimdallClient is an autogenerated mock type for the IHeimdallClient type -type IHeimdallClient struct { - mock.Mock +// MockIHeimdallClient is a mock of IHeimdallClient interface. +type MockIHeimdallClient struct { + ctrl *gomock.Controller + recorder *MockIHeimdallClientMockRecorder } -// Close provides a mock function with given fields: -func (_m *IHeimdallClient) Close() { - _m.Called() +// MockIHeimdallClientMockRecorder is the mock recorder for MockIHeimdallClient. +type MockIHeimdallClientMockRecorder struct { + mock *MockIHeimdallClient } -// Fetch provides a mock function with given fields: path, query -func (_m *IHeimdallClient) Fetch(path string, query string) (*bor.ResponseWithHeight, error) { - ret := _m.Called(path, query) - - var r0 *bor.ResponseWithHeight - if rf, ok := ret.Get(0).(func(string, string) *bor.ResponseWithHeight); ok { - r0 = rf(path, query) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*bor.ResponseWithHeight) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(path, query) - } else { - r1 = ret.Error(1) - } - - return r0, r1 +// NewMockIHeimdallClient creates a new mock instance. +func NewMockIHeimdallClient(ctrl *gomock.Controller) *MockIHeimdallClient { + mock := &MockIHeimdallClient{ctrl: ctrl} + mock.recorder = &MockIHeimdallClientMockRecorder{mock} + return mock } -// FetchStateSyncEvents provides a mock function with given fields: fromID, to -func (_m *IHeimdallClient) FetchStateSyncEvents(fromID uint64, to int64) ([]*bor.EventRecordWithTime, error) { - ret := _m.Called(fromID, to) - - var r0 []*bor.EventRecordWithTime - if rf, ok := ret.Get(0).(func(uint64, int64) []*bor.EventRecordWithTime); ok { - r0 = rf(fromID, to) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*bor.EventRecordWithTime) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(uint64, int64) error); ok { - r1 = rf(fromID, to) - } else { - r1 = ret.Error(1) - } - - return r0, r1 +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIHeimdallClient) EXPECT() *MockIHeimdallClientMockRecorder { + return m.recorder } -// FetchWithRetry provides a mock function with given fields: path, query -func (_m *IHeimdallClient) FetchWithRetry(path string, query string) (*bor.ResponseWithHeight, error) { - ret := _m.Called(path, query) - - var r0 *bor.ResponseWithHeight - if rf, ok := ret.Get(0).(func(string, string) *bor.ResponseWithHeight); ok { - r0 = rf(path, query) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*bor.ResponseWithHeight) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(path, query) - } else { - r1 = ret.Error(1) - } - - return r0, r1 +// 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)) +} + +// Span mocks base method. +func (m *MockIHeimdallClient) Span(arg0 uint64) (*span.HeimdallSpan, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Span", arg0) + ret0, _ := ret[0].(*span.HeimdallSpan) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Span indicates an expected call of Span. +func (mr *MockIHeimdallClientMockRecorder) Span(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Span", reflect.TypeOf((*MockIHeimdallClient)(nil).Span), arg0) +} + +// StateSyncEvents mocks base method. +func (m *MockIHeimdallClient) StateSyncEvents(arg0 uint64, arg1 int64) ([]*clerk.EventRecordWithTime, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSyncEvents", arg0, arg1) + 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 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSyncEvents", reflect.TypeOf((*MockIHeimdallClient)(nil).StateSyncEvents), arg0, arg1) } diff --git a/tests/deps/fake.go b/tests/deps/fake.go new file mode 100644 index 0000000000..cd04ffa070 --- /dev/null +++ b/tests/deps/fake.go @@ -0,0 +1,7 @@ +package deps + +// it is a fake file to lock deps +//nolint:typecheck +import ( + _ "github.com/golang/mock/mockgen/model" +) diff --git a/trie/hasher.go b/trie/hasher.go index 7f0748c13d..c085970a3f 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -19,9 +19,10 @@ package trie import ( "sync" + "golang.org/x/crypto/sha3" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) // hasher is a type used for the trie Hash operation. A hasher has some