diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da33bd0507..05451e03aa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,8 +19,7 @@ jobs: go-version: 1.17.x - name: Build Dogechain - # run: go build -tags netgo -ldflags="-s -w -linkmode external -extldflags "-static" -X \"github.com/dogechain-lab/dogechain/versioning.Version=${GITHUB_REF_NAME}\" -X \"github.com/dogechain-lab/dogechain/versioning.Commit=${GITHUB_SHA}\"" && tar -czvf dogechain.tar.gz dogechain - run: go build -a -o dogechain . && tar -czvf dogechain.tar.gz dogechain + run: go build -ldflags="-X \"github.com/dogechain-lab/dogechain/versioning.Version=${GITHUB_REF_NAME}\" -X \"github.com/dogechain-lab/dogechain/versioning.Commit=${GITHUB_SHA}\"" -a -o dogechain . && tar -czvf dogechain.tar.gz dogechain env: CGO_ENABLED: 0 CC: gcc diff --git a/.golangci.yml b/.golangci.yml index 808fc7ab32..17b704c548 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,34 +1,65 @@ +run: + timeout: 3m + tests: true + # default is true. Enables skipping of directories: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + skip-dirs-use-default: true + +service: + golangci-lint-version: 1.49.0 + linters: + # default false + disable-all: true # Enable specific linter # https://golangci-lint.run/usage/linters/#enabled-by-default-linters enable: - - dogsled - - dupl - - errname - - errorlint - - forcetypeassert - - goconst - - gofmt - - gosec - - importas - - lll - - makezero - - misspell - - nlreturn - - nolintlint - - prealloc - - predeclared - - stylecheck - - thelper - - tparallel - - unconvert - - wastedassign - - whitespace - - wsl + - dogsled # Checks assignments with too many blank identifiers (e.g. x, , , _, := f()) + - dupl # Code clone detection + - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - forcetypeassert # Finds forced type assertions + - goconst # Repeated strings that could be replaced by a constant + - gofmt # Whether the code was gofmt-ed + - goimports # Unused imports + - gosec # Security problems + - importas # Enforces consistent import aliases + - lll # Long lines + - makezero # Finds slice declarations with non-zero initial length + - misspell # Misspelled English words in comments + - nlreturn # Checks for a new line before return and branch statements to increase code clarity + - nolintlint # Ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could + - predeclared # Finds code that shadows one of Go's predeclared identifiers + - stylecheck # Stylecheck is a replacement for golint + - thelper # Detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - tparallel # Detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # Unnecessary type conversions + - wastedassign # Finds wasted assignment statements + - whitespace # Tool for detection of leading and trailing whitespace + - wsl # Forces you to use empty lines + +linters-settings: + gofmt: + simplify: true + goconst: + min-len: 3 + min-occurrences: 3 issues: + # new-from-rev: origin/dev # report only new issues with reference to dev branch exclude-rules: + - path: _test\.go + linters: + - gosec + - unparam + - lll - linters: - staticcheck path: "state/runtime/precompiled/base.go" text: "SA1019:" + include: + - EXC0012 # Exported (.+) should have comment( \(or a comment on this block\))? or be unexported + - EXC0013 # Package comment should be of the form "(.+)... + - EXC0014 # Comment on exported (.+) should be of the form "(.+)..." + - EXC0015 # Should have a package comment diff --git a/Makefile b/Makefile index 4d626ff29e..15287884ca 100644 --- a/Makefile +++ b/Makefile @@ -19,13 +19,17 @@ protoc: .PHONY: build build: $(eval LATEST_VERSION = $(shell git describe --tags --abbrev=0)) - $(eval COMMIT_HASH = $(shell git rev-parse --short HEAD)) - $(eval DATE = $(shell date +'%Y-%m-%d_%T')) - go build -o dogechain -ldflags="-X 'github.com/dogechain-lab/dogechain/versioning.Version=$(LATEST_VERSION)+$(COMMIT_HASH)+$(DATE)'" main.go + $(eval COMMIT_HASH = $(shell git rev-parse HEAD)) + $(eval DATE = $(shell date -u +'%Y-%m-%dT%TZ')) + go build -o dogechain -ldflags="\ + -X 'github.com/dogechain-lab/dogechain/versioning.Version=$(LATEST_VERSION)'\ + -X 'github.com/dogechain-lab/dogechain/versioning.Commit=$(COMMIT_HASH)'\ + -X 'github.com/dogechain-lab/dogechain/versioning.BuildTime=$(DATE)'" \ + main.go .PHONY: lint lint: - golangci-lint run -c .golangci.yml --timeout=3m + golangci-lint run -c .golangci.yml .PHONY: test test: build diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 381e8626e2..d2918f8bf6 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -11,6 +11,7 @@ import ( "github.com/dogechain-lab/dogechain/blockchain/storage" "github.com/dogechain-lab/dogechain/chain" "github.com/dogechain-lab/dogechain/contracts/upgrader" + "github.com/dogechain-lab/dogechain/contracts/validatorset" "github.com/dogechain-lab/dogechain/helper/common" "github.com/dogechain-lab/dogechain/state" "github.com/dogechain-lab/dogechain/types" @@ -90,10 +91,13 @@ type Verifier interface { ProcessHeaders(headers []*types.Header) error GetBlockCreator(header *types.Header) (types.Address, error) PreStateCommit(header *types.Header, txn *state.Transition) error + IsSystemTransaction(height uint64, coinbase types.Address, tx *types.Transaction) bool } type Executor interface { - ProcessBlock(parentRoot types.Hash, block *types.Block, blockCreator types.Address) (*state.Transition, error) + BeginTxn(parentRoot types.Hash, header *types.Header, coinbase types.Address) (*state.Transition, error) + //nolint:lll + ProcessTransactions(transition *state.Transition, gasLimit uint64, transactions []*types.Transaction) (*state.Transition, error) Stop() } @@ -841,21 +845,20 @@ func (b *Blockchain) executeBlockTransactions(block *types.Block) (*BlockResult, return nil, ErrParentNotFound } + height := header.Number + blockCreator, err := b.consensus.GetBlockCreator(header) if err != nil { return nil, err } - txn, err := b.executor.ProcessBlock(parent.StateRoot, block, blockCreator) + // prepare execution + txn, err := b.executor.BeginTxn(parent.StateRoot, block.Header, blockCreator) if err != nil { return nil, err } - if err := b.consensus.PreStateCommit(header, txn); err != nil { - return nil, err - } - - // upgrade system if needed + // upgrade system contract first if needed upgrader.UpgradeSystem( b.Config().ChainID, b.Config().Forks, @@ -864,6 +867,31 @@ func (b *Blockchain) executeBlockTransactions(block *types.Block) (*BlockResult, b.logger, ) + // there might be 2 system transactions, slash or deposit + systemTxs := make([]*types.Transaction, 0, 2) + // normal transactions which is not consensus associated + normalTxs := make([]*types.Transaction, 0, len(block.Transactions)) + + // the include sequence should be same as execution, otherwise it failed on state root comparison + for _, tx := range block.Transactions { + if b.consensus.IsSystemTransaction(height, blockCreator, tx) { + systemTxs = append(systemTxs, tx) + + continue + } + + normalTxs = append(normalTxs, tx) + } + + // execute normal transaction first + if _, err := b.executor.ProcessTransactions(txn, header.GasLimit, normalTxs); err != nil { + return nil, err + } + + if _, err := b.executor.ProcessTransactions(txn, header.GasLimit, systemTxs); err != nil { + return nil, err + } + if b.isStopped() { // execute stop, should not commit return nil, ErrClosed @@ -1015,8 +1043,9 @@ func (b *Blockchain) writeBody(block *types.Block) error { } // Write txn lookups (txHash -> block) - for _, txn := range block.Transactions { - if err := b.db.WriteTxLookup(txn.Hash, block.Hash()); err != nil { + for _, tx := range block.Transactions { + // write hash lookup + if err := b.db.WriteTxLookup(tx.Hash(), block.Hash()); err != nil { return err } } @@ -1052,10 +1081,17 @@ func (b *Blockchain) verifyGasLimit(header, parentHeader *types.Header) error { diff *= -1 } + // gas limit should not count system transactions limit := parentHeader.GasLimit / BlockGasTargetDivisor + // system transactions after detroit fork + if b.Config().Forks.IsDetroit(header.Number) { + // might be 2 txs. + limit += 2 * validatorset.SystemTransactionGasLimit + } + if uint64(diff) > limit { return fmt.Errorf( - "invalid gas limit, limit = %d, want %d +- %d", + "limit = %d, want %d +- %d", header.GasLimit, parentHeader.GasLimit, limit-1, diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index 907c426d97..2a8e2a9365 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -971,10 +971,10 @@ func TestBlockchain_VerifyBlockBody(t *testing.T) { executorCallback := func(executor *mockExecutor) { // This is executor processing - executor.HookProcessBlock(func( - hash types.Hash, - block *types.Block, - address types.Address, + executor.HookProcessTransction(func( + transition *state.Transition, + gasLimit uint64, + txs []*types.Transaction, ) (*state.Transition, error) { return nil, errUnableToExecute }) diff --git a/blockchain/storage/testing.go b/blockchain/storage/testing.go index 5c0eb17a9e..56fb7be68e 100644 --- a/blockchain/storage/testing.go +++ b/blockchain/storage/testing.go @@ -279,7 +279,7 @@ func testBody(t *testing.T, m PlaceholderStorage) { Input: []byte{1, 2}, V: big.NewInt(1), } - t0.ComputeHash() + t0.Hash() addr2 := types.StringToAddress("22") t1 := &types.Transaction{ @@ -291,7 +291,7 @@ func testBody(t *testing.T, m PlaceholderStorage) { Input: []byte{4, 5}, V: big.NewInt(2), } - t1.ComputeHash() + t1.Hash() block := types.Block{ Header: header, @@ -313,7 +313,7 @@ func testBody(t *testing.T, m PlaceholderStorage) { } for indx, i := range tx0 { - if i.Hash != tx1[indx].Hash { + if i.Hash() != tx1[indx].Hash() { t.Fatal("tx not correct") } } @@ -351,7 +351,7 @@ func testReceipts(t *testing.T, m PlaceholderStorage) { r0 := &types.Receipt{ Root: types.StringToHash("1"), CumulativeGasUsed: 10, - TxHash: txn.Hash, + TxHash: txn.Hash(), LogsBloom: types.Bloom{0x1}, Logs: []*types.Log{ { @@ -368,7 +368,7 @@ func testReceipts(t *testing.T, m PlaceholderStorage) { r1 := &types.Receipt{ Root: types.StringToHash("1"), CumulativeGasUsed: 10, - TxHash: txn.Hash, + TxHash: txn.Hash(), LogsBloom: types.Bloom{0x1}, GasUsed: 10, ContractAddress: &types.Address{0x1}, diff --git a/blockchain/testing.go b/blockchain/testing.go index f80df73a3c..dbf807eaaa 100644 --- a/blockchain/testing.go +++ b/blockchain/testing.go @@ -105,7 +105,7 @@ func NewTestBlockchain(t *testing.T, headers []*types.Header) *Blockchain { }, } - st := itrie.NewState(itrie.NewMemoryStorage()) + st := itrie.NewState(itrie.NewMemoryStorage(), nil) b, err := newBlockChain(config, state.NewExecutor(config.Params, st, hclog.NewNullLogger())) if err != nil { @@ -277,24 +277,36 @@ func (m *MockVerifier) PreStateCommit(header *types.Header, txn *state.Transitio return nil } +func (m *MockVerifier) IsSystemTransaction(height uint64, coinbase types.Address, tx *types.Transaction) bool { + return false +} + func (m *MockVerifier) HookPreStateCommit(fn preStateCommitDelegate) { m.preStateCommitFn = fn } // Executor delegators -type processBlockDelegate func(types.Hash, *types.Block, types.Address) (*state.Transition, error) +type processTransactionDelegate func(*state.Transition, uint64, []*types.Transaction) (*state.Transition, error) type mockExecutor struct { - processBlockFn processBlockDelegate + processTransactionFn processTransactionDelegate } -func (m *mockExecutor) ProcessBlock( +func (m *mockExecutor) BeginTxn( parentRoot types.Hash, - block *types.Block, - blockCreator types.Address, + header *types.Header, + coinbaseReceiver types.Address, +) (*state.Transition, error) { + return &state.Transition{}, nil +} + +func (m *mockExecutor) ProcessTransactions( + transition *state.Transition, + gasLimit uint64, + transactions []*types.Transaction, ) (*state.Transition, error) { - if m.processBlockFn != nil { - return m.processBlockFn(parentRoot, block, blockCreator) + if m.processTransactionFn != nil { + return m.processTransactionFn(transition, gasLimit, transactions) } return nil, nil @@ -304,8 +316,8 @@ func (m *mockExecutor) Stop() { // do nothing } -func (m *mockExecutor) HookProcessBlock(fn processBlockDelegate) { - m.processBlockFn = fn +func (m *mockExecutor) HookProcessTransction(fn processTransactionDelegate) { + m.processTransactionFn = fn } func TestBlockchain(t *testing.T, genesis *chain.Genesis) *Blockchain { diff --git a/chain/params.go b/chain/params.go index 64fe50e55b..cf6bca31dc 100644 --- a/chain/params.go +++ b/chain/params.go @@ -32,7 +32,9 @@ type Forks struct { EIP150 *Fork `json:"EIP150,omitempty"` EIP158 *Fork `json:"EIP158,omitempty"` EIP155 *Fork `json:"EIP155,omitempty"` - Portland *Fork `json:"portland,omitempty"` + Preportland *Fork `json:"pre-portland,omitempty"` // test hardfork only in some test networks + Portland *Fork `json:"portland,omitempty"` // bridge hardfork + Detroit *Fork `json:"detroit,omitempty"` // pos hardfork } func (f *Forks) on(ff *Fork, block uint64) bool { @@ -83,6 +85,10 @@ func (f *Forks) IsPortland(block uint64) bool { return f.active(f.Portland, block) } +func (f *Forks) IsDetroit(block uint64) bool { + return f.active(f.Detroit, block) +} + func (f *Forks) At(block uint64) ForksInTime { return ForksInTime{ Homestead: f.active(f.Homestead, block), @@ -93,14 +99,24 @@ func (f *Forks) At(block uint64) ForksInTime { EIP150: f.active(f.EIP150, block), EIP158: f.active(f.EIP158, block), EIP155: f.active(f.EIP155, block), + Preportland: f.active(f.Preportland, block), Portland: f.active(f.Portland, block), + Detroit: f.active(f.Detroit, block), } } +func (f *Forks) IsOnPreportland(block uint64) bool { + return f.on(f.Preportland, block) +} + func (f *Forks) IsOnPortland(block uint64) bool { return f.on(f.Portland, block) } +func (f *Forks) IsOnDetroit(block uint64) bool { + return f.on(f.Detroit, block) +} + type Fork uint64 func NewFork(n uint64) *Fork { @@ -130,7 +146,9 @@ type ForksInTime struct { EIP150, EIP158, EIP155, - Portland bool + Preportland, + Portland, + Detroit bool } var AllForksEnabled = &Forks{ @@ -142,5 +160,7 @@ var AllForksEnabled = &Forks{ Constantinople: NewFork(0), Petersburg: NewFork(0), Istanbul: NewFork(0), + Preportland: NewFork(10000), Portland: NewFork(10222), + Detroit: NewFork(40562), } diff --git a/command/helper/helper.go b/command/helper/helper.go index 37ad3d3d00..8f2c5cf506 100644 --- a/command/helper/helper.go +++ b/command/helper/helper.go @@ -249,6 +249,13 @@ func RegisterPprofFlag(cmd *cobra.Command) { ) } +// GetPprofFlag extracts the +func GetPprofFlag(cmd *cobra.Command) bool { + v, _ := cmd.Flags().GetBool(command.PprofFlag) + + return v +} + // ParseGraphQLAddress parses the passed in GraphQL address func ParseGraphQLAddress(graphqlAddress string) (*url.URL, error) { return url.ParseRequestURI(graphqlAddress) diff --git a/command/server/config.go b/command/server/config.go index 443aa16f9e..eddfa7b707 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -34,7 +34,8 @@ type Config struct { JSONRPCBatchRequestLimit uint64 `json:"json_rpc_batch_request_limit" yaml:"json_rpc_batch_request_limit"` JSONRPCBlockRangeLimit uint64 `json:"json_rpc_block_range_limit" yaml:"json_rpc_block_range_limit"` JSONNamespace string `json:"json_namespace" yaml:"json_namespace"` - EnableWS bool `json:"enable_ws"` + EnableWS bool `json:"enable_ws" yaml:"enable_ws"` + EnablePprof bool `json:"enable_pprof" yaml:"enable_pprof"` } // Telemetry holds the config details for metric services. @@ -103,6 +104,7 @@ func DefaultConfig() *Config { JSONRPCBlockRangeLimit: jsonrpc.DefaultJSONRPCBlockRangeLimit, JSONNamespace: string(jsonrpc.NamespaceAll), EnableWS: false, + EnablePprof: false, } } diff --git a/command/server/params.go b/command/server/params.go index fb35be8b26..a939a090a3 100644 --- a/command/server/params.go +++ b/command/server/params.go @@ -174,6 +174,7 @@ func (p *serverParams) generateConfig() *server.Config { chainCfg.Params.BlockGasTarget = p.blockGasTarget } + // namespace ns := strings.Split(p.rawConfig.JSONNamespace, ",") return &server.Config{ @@ -185,12 +186,14 @@ func (p *serverParams) generateConfig() *server.Config { BlockRangeLimit: p.rawConfig.JSONRPCBlockRangeLimit, JSONNamespace: ns, EnableWS: p.rawConfig.EnableWS, + EnablePprof: p.rawConfig.EnablePprof, }, EnableGraphQL: p.rawConfig.EnableGraphQL, GraphQL: &server.GraphQL{ GraphQLAddr: p.graphqlAddress, AccessControlAllowOrigin: p.corsAllowedOrigins, BlockRangeLimit: p.rawConfig.JSONRPCBlockRangeLimit, + EnablePprof: p.rawConfig.EnablePprof, }, GRPCAddr: p.grpcAddress, LibP2PAddr: p.libp2pAddress, diff --git a/command/server/server.go b/command/server/server.go index 72239d7184..9e0805608e 100644 --- a/command/server/server.go +++ b/command/server/server.go @@ -459,6 +459,9 @@ func runCommand(cmd *cobra.Command, _ []string) { log.Println("Child process ", os.Getpid(), "ValidatorKey len: ", len(params.validatorKey)) } + // pprof flag + params.rawConfig.EnablePprof = helper.GetPprofFlag(cmd) + if err := runServerLoop(params.generateConfig(), outputter); err != nil { outputter.SetError(err) outputter.WriteOutput() diff --git a/command/version/result.go b/command/version/result.go index 4da1e21334..b62d3e8744 100644 --- a/command/version/result.go +++ b/command/version/result.go @@ -1,9 +1,27 @@ package version +import ( + "fmt" + "strings" + + "github.com/dogechain-lab/dogechain/command/helper" +) + type VersionResult struct { - Version string `json:"version"` + Version string `json:"version"` + Commit string `json:"commit"` + BuildTime string `json:"buildTime"` } func (r *VersionResult) GetOutput() string { - return r.Version + var s strings.Builder + + s.WriteString("Dogechain\n") + s.WriteString(helper.FormatKV([]string{ + fmt.Sprintf("Version|%s", r.Version), + fmt.Sprintf("Commit|%s", r.Commit), + fmt.Sprintf("Build Time|%s", r.BuildTime), + })) + + return s.String() } diff --git a/command/version/version.go b/command/version/version.go index 3cb1214c4d..16fe802421 100644 --- a/command/version/version.go +++ b/command/version/version.go @@ -21,7 +21,9 @@ func runCommand(cmd *cobra.Command, _ []string) { outputter.SetCommandResult( &VersionResult{ - Version: versioning.Version, + Version: versioning.Version, + Commit: versioning.Commit, + BuildTime: versioning.BuildTime, }, ) } diff --git a/consensus/consensus.go b/consensus/consensus.go index ef72ed1c2a..c9228dc7f2 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -42,6 +42,9 @@ type Consensus interface { // Close closes the connection Close() error + + // Is a transaction systemctem transaction on a specific block height and coinbase + IsSystemTransaction(height uint64, coinbase types.Address, tx *types.Transaction) bool } // Config is the configuration for the consensus diff --git a/consensus/dev/dev.go b/consensus/dev/dev.go index f5c3ae9d9f..ff7320e6a4 100644 --- a/consensus/dev/dev.go +++ b/consensus/dev/dev.go @@ -145,21 +145,20 @@ func (d *Dev) writeTransactions(gasLimit uint64, transition transitionInterface) } else if nonceErr, ok := err.(*state.NonceTooLowError); ok { // low nonce tx, should reset accounts once done d.logger.Warn("write transaction nonce too low", - "hash", tx.Hash, "from", tx.From, "nonce", tx.Nonce) + "hash", tx.Hash(), "from", tx.From, "nonce", tx.Nonce) // skip the address, whose txs should be reset first. d.txpool.DemoteAllPromoted(tx, nonceErr.CorrectNonce) priceTxs.Pop() } else if nonceErr, ok := err.(*state.NonceTooHighError); ok { // high nonce tx, should reset accounts once done d.logger.Error("write miss some transactions with higher nonce", - tx.Hash, "from", tx.From, "nonce", tx.Nonce) + "hash", tx.Hash(), "from", tx.From, "nonce", tx.Nonce) d.txpool.DemoteAllPromoted(tx, nonceErr.CorrectNonce) priceTxs.Pop() } else { // no matter what kind of failure, drop is reasonable for not executed it yet d.logger.Debug("write not executed transaction failed", - "hash", tx.Hash, "from", tx.From, - "nonce", tx.Nonce, "err", err) + "hash", tx.Hash(), "from", tx.From, "nonce", tx.Nonce, "err", err) d.txpool.Drop(tx) priceTxs.Pop() } @@ -209,8 +208,6 @@ func (d *Dev) writeNewBlock(parent *types.Header) error { return err } - txns := d.writeTransactions(gasLimit, transition) - // upgrade system if needed upgrader.UpgradeSystem( d.blockchain.Config().ChainID, @@ -220,6 +217,8 @@ func (d *Dev) writeNewBlock(parent *types.Header) error { d.logger, ) + txns := d.writeTransactions(gasLimit, transition) + // Commit the changes _, root := transition.Commit() @@ -271,6 +270,10 @@ func (d *Dev) PreStateCommit(_header *types.Header, _txn *state.Transition) erro return nil } +func (d *Dev) IsSystemTransaction(height uint64, coinbase types.Address, tx *types.Transaction) bool { + return false +} + func (d *Dev) GetSyncProgression() *progress.Progression { return nil } diff --git a/consensus/dummy/dummy.go b/consensus/dummy/dummy.go index 75b9366369..92138962a7 100644 --- a/consensus/dummy/dummy.go +++ b/consensus/dummy/dummy.go @@ -65,6 +65,10 @@ func (d *Dummy) PreStateCommit(_header *types.Header, _txn *state.Transition) er return nil } +func (d *Dummy) IsSystemTransaction(height uint64, coinbase types.Address, tx *types.Transaction) bool { + return false +} + func (d *Dummy) GetSyncProgression() *progress.Progression { return nil } diff --git a/consensus/ibft/currentstate/state.go b/consensus/ibft/currentstate/state.go new file mode 100644 index 0000000000..dd4a9ee3e6 --- /dev/null +++ b/consensus/ibft/currentstate/state.go @@ -0,0 +1,328 @@ +package currentstate + +import ( + "fmt" + "math" + "sync/atomic" + "time" + + "github.com/dogechain-lab/dogechain/consensus/ibft/proto" + "github.com/dogechain-lab/dogechain/consensus/ibft/validator" + "github.com/dogechain-lab/dogechain/types" +) + +type IbftState uint32 + +// Define the states in IBFT +const ( + AcceptState IbftState = iota + RoundChangeState + ValidateState + CommitState + SyncState +) + +// String returns the string representation of the passed in state +func (i IbftState) String() string { + switch i { + case AcceptState: + return "AcceptState" + + case RoundChangeState: + return "RoundChangeState" + + case ValidateState: + return "ValidateState" + + case CommitState: + return "CommitState" + + case SyncState: + return "SyncState" + } + + panic(fmt.Sprintf("BUG: Ibft state not found %d", i)) +} + +// CurrentState defines the current state object in IBFT +type CurrentState struct { + // validators represent the current validator set + validators validator.Validators + + // state is the current state + state uint64 + + // The proposed block + block *types.Block + + // The selected proposer + proposer types.Address + + // Current view + view *proto.View + + // List of prepared messages + prepared map[types.Address]*proto.MessageReq + + // List of committed messages + committed map[types.Address]*proto.MessageReq + + // List of round change messages + roundMessages map[uint64]map[types.Address]*proto.MessageReq + + // Locked signals whether the proposal is locked + locked bool + + // Describes whether there has been an error during the computation + err error +} + +// NewState creates a new state with reset round messages +func NewState() *CurrentState { + c := &CurrentState{} + c.ResetRoundMsgs() + + return c +} + +// GetState returns the current state +func (c *CurrentState) GetState() IbftState { + stateAddr := &c.state + + return IbftState(atomic.LoadUint64(stateAddr)) +} + +// SetState sets the current state +func (c *CurrentState) SetState(s IbftState) { + stateAddr := &c.state + + atomic.StoreUint64(stateAddr, uint64(s)) +} + +func (c *CurrentState) SetBlock(b *types.Block) { + c.block = b +} + +func (c *CurrentState) Block() *types.Block { + return c.block +} + +func (c *CurrentState) SetValidators(validators []types.Address) { + c.validators = validators +} + +func (c *CurrentState) Validators() []types.Address { + return c.validators +} + +// NumValid returns the number of required messages +func (c *CurrentState) NumValid() int { + // According to the IBFT spec, the number of valid messages + // needs to be 2F + 1 + // The 1 missing from this equation is accounted for elsewhere + // (the current node tallying the messages will include its own message) + return 2 * c.MaxFaultyNodes() +} + +func (c *CurrentState) MaxFaultyNodes() int { + return c.validators.MaxFaultyNodes() +} + +func (c *CurrentState) HandleErr(err error) { + c.err = err +} + +// ConsumeErr returns the current error, if any, and consumes it +func (c *CurrentState) ConsumeErr() error { + err := c.err + c.err = nil + + return err +} + +func (c *CurrentState) PeekError() error { + return c.err +} + +func (c *CurrentState) Sequence() uint64 { + if c.view != nil { + return c.view.Sequence + } + + return 0 +} + +func (c *CurrentState) Round() uint64 { + if c.view != nil { + return c.view.Round + } + + return 0 +} + +func (c *CurrentState) NextRound() uint64 { + return c.view.Round + 1 +} + +func (c *CurrentState) MaxRound() (maxRound uint64, found bool) { + num := c.validators.MaxFaultyNodes() + 1 + + for k, round := range c.roundMessages { + if len(round) < num { + continue + } + + if maxRound < k { + maxRound = k + found = true + } + } + + return +} + +const ( + baseTimeout = 10 * time.Second + maxTimeout = 300 * time.Second +) + +// MessageTimeout returns duration for waiting message +// +// Consider the network travel time between most validators, using validator +// numbers instead of rounds. +func (c *CurrentState) MessageTimeout() time.Duration { + if c.Round() >= 8 { + return maxTimeout + } + + timeout := baseTimeout + if c.Round() > 0 { + timeout += time.Duration(int64(math.Pow(2, float64(c.Round())))) * time.Second + } + + return timeout +} + +// ResetRoundMsgs resets the prepared, committed and round messages in the current state +func (c *CurrentState) ResetRoundMsgs() { + c.prepared = map[types.Address]*proto.MessageReq{} + c.committed = map[types.Address]*proto.MessageReq{} + c.roundMessages = map[uint64]map[types.Address]*proto.MessageReq{} +} + +// CalcProposer calculates the proposer and sets it to the state +func (c *CurrentState) CalcProposer(lastProposer types.Address) { + c.proposer = c.validators.CalcProposer(c.view.Round, lastProposer) +} + +func (c *CurrentState) Proposer() types.Address { + return c.proposer +} + +func (c *CurrentState) CalcNeedPunished( + currentRound uint64, + lastBlockProposer types.Address, +) (addr []types.Address) { + if currentRound == 0 { + // no one need to be punished + return nil + } + + // only punish the first validator, + p := c.validators.CalcProposer(0, lastBlockProposer) + addr = append(addr, p) + + return addr +} + +func (c *CurrentState) SetView(view *proto.View) { + c.view = view +} + +func (c *CurrentState) View() *proto.View { + return c.view +} + +func (c *CurrentState) IsLocked() bool { + return c.locked +} + +func (c *CurrentState) Lock() { + c.locked = true +} + +func (c *CurrentState) Unlock() { + c.block = nil + c.locked = false +} + +// CleanRound deletes the specific round messages +func (c *CurrentState) CleanRound(round uint64) { + delete(c.roundMessages, round) +} + +// AddRoundMessage adds a message to the round, and returns the round message size +func (c *CurrentState) AddRoundMessage(msg *proto.MessageReq) int { + if msg.Type != proto.MessageReq_RoundChange { + return 0 + } + + c.AddMessage(msg) + + return len(c.roundMessages[msg.View.Round]) +} + +// AddPrepared adds a prepared message +func (c *CurrentState) AddPrepared(msg *proto.MessageReq) { + if msg.Type != proto.MessageReq_Prepare { + return + } + + c.AddMessage(msg) +} + +// AddCommitted adds a committed message +func (c *CurrentState) AddCommitted(msg *proto.MessageReq) { + if msg.Type != proto.MessageReq_Commit { + return + } + + c.AddMessage(msg) +} + +func (c *CurrentState) Committed() map[types.Address]*proto.MessageReq { + return c.committed +} + +// AddMessage adds a new message to one of the following message lists: committed, prepared, roundMessages +func (c *CurrentState) AddMessage(msg *proto.MessageReq) { + addr := msg.FromAddr() + if !c.validators.Includes(addr) { + // only include messages from validators + return + } + + switch { + case msg.Type == proto.MessageReq_Commit: + c.committed[addr] = msg + case msg.Type == proto.MessageReq_Prepare: + c.prepared[addr] = msg + case msg.Type == proto.MessageReq_RoundChange: + view := msg.View + if _, ok := c.roundMessages[view.Round]; !ok { + c.roundMessages[view.Round] = map[types.Address]*proto.MessageReq{} + } + + c.roundMessages[view.Round][addr] = msg + } +} + +// NumPrepared returns the number of messages in the prepared message list +func (c *CurrentState) NumPrepared() int { + return len(c.prepared) +} + +// numCommitted returns the number of messages in the committed message list +func (c *CurrentState) NumCommitted() int { + return len(c.committed) +} diff --git a/consensus/ibft/currentstate/state_test.go b/consensus/ibft/currentstate/state_test.go new file mode 100644 index 0000000000..1a3d38f8b9 --- /dev/null +++ b/consensus/ibft/currentstate/state_test.go @@ -0,0 +1,117 @@ +package currentstate + +import ( + "testing" + "time" + + "github.com/dogechain-lab/dogechain/consensus/ibft/proto" + "github.com/dogechain-lab/dogechain/consensus/ibft/validator" + "github.com/dogechain-lab/dogechain/types" + "github.com/stretchr/testify/assert" +) + +func TestState_PorposerAndNeedPunished(t *testing.T) { + t.Parallel() + + var ( + v1 = types.StringToAddress("0x1") + v2 = types.StringToAddress("0x2") + v3 = types.StringToAddress("0x3") + v4 = types.StringToAddress("0x4") + ) + + state := NewState() + state.validators = validator.Validators{v1, v2, v3, v4} + + tests := []struct { + name string + round uint64 + lastBlockProposer types.Address + supporseProposer types.Address + needPunished []types.Address + }{ + { + name: "round 0 should not punish anyone", + round: 0, + lastBlockProposer: v1, + supporseProposer: v2, + needPunished: nil, + }, + { + name: "round 2 should punish first validator", + round: 2, + lastBlockProposer: v3, + supporseProposer: v2, + needPunished: []types.Address{v4}, + }, + { + name: "large round should punish first validator", + round: 9, + lastBlockProposer: v2, + supporseProposer: v4, + needPunished: []types.Address{v3}, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + proposer := state.validators.CalcProposer(tt.round, tt.lastBlockProposer) + assert.Equal(t, tt.supporseProposer, proposer) + + punished := state.CalcNeedPunished(tt.round, tt.lastBlockProposer) + assert.Equal(t, tt.needPunished, punished) + }) + } +} + +func TestState_MessageTimeout(t *testing.T) { + testCases := []struct { + description string + c *CurrentState + expected time.Duration + }{ + { + description: "round 0 return 10s", + c: &CurrentState{view: proto.ViewMsg(1, 0)}, + expected: baseTimeout, + }, + { + description: "round 1 returns 12s", + c: &CurrentState{view: proto.ViewMsg(1, 1)}, + expected: baseTimeout + 2*time.Second, + }, + { + description: "round 3 returns 18s", + c: &CurrentState{view: proto.ViewMsg(1, 3)}, + expected: baseTimeout + 8*time.Second, + }, + { + description: "round 7 returns 138s", + c: &CurrentState{view: proto.ViewMsg(1, 7)}, + expected: baseTimeout + 128*time.Second, + }, + { + description: "round 8 returns 300s", + c: &CurrentState{view: proto.ViewMsg(1, 8)}, + expected: maxTimeout, + }, + { + description: "round 9 returns 300s", + c: &CurrentState{view: proto.ViewMsg(1, 9)}, + expected: maxTimeout, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.description, func(t *testing.T) { + timeout := test.c.MessageTimeout() + + assert.Equal(t, test.expected, timeout) + }) + } +} diff --git a/consensus/ibft/ibft.go b/consensus/ibft/ibft.go index 4ae2677fcc..0ac614b4aa 100644 --- a/consensus/ibft/ibft.go +++ b/consensus/ibft/ibft.go @@ -8,11 +8,13 @@ import ( "reflect" "time" - "go.uber.org/atomic" - "github.com/dogechain-lab/dogechain/consensus" + "github.com/dogechain-lab/dogechain/consensus/ibft/currentstate" "github.com/dogechain-lab/dogechain/consensus/ibft/proto" + "github.com/dogechain-lab/dogechain/consensus/ibft/validator" + "github.com/dogechain-lab/dogechain/contracts/systemcontracts" "github.com/dogechain-lab/dogechain/contracts/upgrader" + "github.com/dogechain-lab/dogechain/contracts/validatorset" "github.com/dogechain-lab/dogechain/crypto" "github.com/dogechain-lab/dogechain/helper/common" "github.com/dogechain-lab/dogechain/helper/hex" @@ -23,6 +25,7 @@ import ( "github.com/dogechain-lab/dogechain/state" "github.com/dogechain-lab/dogechain/types" "github.com/hashicorp/go-hclog" + "go.uber.org/atomic" "google.golang.org/grpc" anypb "google.golang.org/protobuf/types/known/anypb" ) @@ -33,9 +36,14 @@ const ( ) var ( - ErrInvalidHookParam = errors.New("invalid IBFT hook param passed in") - ErrInvalidMechanismType = errors.New("invalid consensus mechanism type in params") - ErrMissingMechanismType = errors.New("missing consensus mechanism type in params") + ErrInvalidHookParam = errors.New("invalid IBFT hook param passed in") + ErrInvalidMechanismType = errors.New("invalid consensus mechanism type in params") + ErrMissingMechanismType = errors.New("missing consensus mechanism type in params") + ErrEmptyValidatorExtract = errors.New("empty extract validatorset") + ErrInvalidMixHash = errors.New("invalid mixhash") + ErrInvalidUncleHash = errors.New("invalid uncle hash") + ErrWrongDifficulty = errors.New("wrong difficulty") + ErrInvalidBlockTimestamp = errors.New("invalid block timestamp") ) type blockchainInterface interface { @@ -66,10 +74,10 @@ type syncerInterface interface { type Ibft struct { sealing bool // Flag indicating if the node is a sealer - logger hclog.Logger // Output logger - config *consensus.Config // Consensus configuration - Grpc *grpc.Server // gRPC configuration - state *currentState // Reference to the current state + logger hclog.Logger // Output logger + config *consensus.Config // Consensus configuration + Grpc *grpc.Server // gRPC configuration + state *currentstate.CurrentState // Reference to the current state blockchain blockchainInterface // Interface exposed by the blockchain layer executor *state.Executor // Reference to the state executor @@ -105,6 +113,9 @@ type Ibft struct { blockTime time.Duration // Minimum block generation time in seconds + // Dynamic References for signing and validating + currentTxSigner crypto.TxSigner // Tx Signer at current sequence + currentValidators validator.Validators // Validator set at current sequence // for banishing some exhausting contracts banishAbnormalContract bool exhaustingContracts map[types.Address]struct{} @@ -180,7 +191,7 @@ func Factory( closeCh: make(chan struct{}), isClosed: atomic.NewBool(false), txpool: params.Txpool, - state: ¤tState{}, + state: ¤tstate.CurrentState{}, network: params.Network, epochSize: epochSize, sealing: params.Seal, @@ -211,6 +222,11 @@ func (i *Ibft) Initialize() error { return err } + // set up current module cache + if err := i.updateCurrentModules(i.blockchain.Header().Number + 1); err != nil { + return err + } + return nil } @@ -340,6 +356,11 @@ func (i *Ibft) setupTransport() error { // Subscribe to the newly created topic err = topic.Subscribe(func(obj interface{}) { + if !i.isActiveValidator(i.validatorKeyAddr) { + // we're not active validator, don't ever care about any ibft messages + return + } + msg, ok := obj.(*proto.MessageReq) if !ok { i.logger.Error("invalid type assertion for message request") @@ -347,12 +368,6 @@ func (i *Ibft) setupTransport() error { return } - if !i.isSealing() { - // if we are not sealing we do not care about the messages - // but we need to subscribe to propagate the messages - return - } - // decode sender if err := validateMsg(msg); err != nil { i.logger.Error("failed to validate msg", "err", err) @@ -360,6 +375,12 @@ func (i *Ibft) setupTransport() error { return } + if !i.isActiveValidator(msg.FromAddr()) { + // TODO: punish bad node + // ignore message from non-validator + return + } + if msg.From == i.validatorKeyAddr.String() { // we are the sender, skip this message since we already // relay our own messages internally. @@ -425,7 +446,7 @@ const IbftKeyName = "validator.key" func (i *Ibft) start() { // consensus always starts in SyncState mode in case it needs // to sync with other nodes. - i.setState(SyncState) + i.setState(currentstate.SyncState) // Grab the latest header header := i.blockchain.Header() @@ -446,22 +467,24 @@ func (i *Ibft) start() { // runCycle represents the IBFT state machine loop func (i *Ibft) runCycle() { // Log to the console - if i.state.view != nil { - i.logger.Debug("cycle", "state", i.getState(), "sequence", i.state.view.Sequence, "round", i.state.view.Round+1) - } + i.logger.Debug("cycle", + "state", i.getState(), + "sequence", i.state.Sequence(), + "round", i.state.Round()+1, + ) // Based on the current state, execute the corresponding section switch i.getState() { - case AcceptState: + case currentstate.AcceptState: i.runAcceptState() - case ValidateState: + case currentstate.ValidateState: i.runValidateState() - case RoundChangeState: + case currentstate.RoundChangeState: i.runRoundChangeState() - case SyncState: + case currentstate.SyncState: i.runSyncState() } } @@ -493,10 +516,21 @@ func (i *Ibft) isValidSnapshot() bool { func (i *Ibft) runSyncState() { // updateSnapshotCallback keeps the snapshot store in sync with the updated // chain data, by calling the SyncStateHook - callInsertBlockHook := func(blockNumber uint64) { + callInsertBlockHook := func(block *types.Block) { + blockNumber := block.Number() + + // insert block if hookErr := i.runHook(InsertBlockHook, blockNumber, blockNumber); hookErr != nil { i.logger.Error(fmt.Sprintf("Unable to run hook %s, %v", InsertBlockHook, hookErr)) } + + // update module cache + if err := i.updateCurrentModules(blockNumber + 1); err != nil { + i.logger.Error("failed to update sub modules", "height", blockNumber+1, "err", err) + } + + // reset headers of txpool + i.txpool.ResetWithHeaders(block.Header) } // save current height to check whether new blocks are added or not during syncing @@ -505,7 +539,7 @@ func (i *Ibft) runSyncState() { beginningHeight = header.Number } - for i.isState(SyncState) { + for i.isState(currentstate.SyncState) { // try to sync with the best-suited peer p := i.syncer.BestPeer() if p == nil { @@ -517,9 +551,9 @@ func (i *Ibft) runSyncState() { i.startNewSequence() //Set the round metric - i.metrics.Rounds.Set(float64(i.state.view.Round)) + i.metrics.Rounds.Set(float64(i.state.Round())) - i.setState(AcceptState) + i.setState(currentstate.AcceptState) } else { time.Sleep(1 * time.Second) } @@ -528,8 +562,7 @@ func (i *Ibft) runSyncState() { } if err := i.syncer.BulkSyncWithPeer(p, func(newBlock *types.Block) { - callInsertBlockHook(newBlock.Number()) - i.txpool.ResetWithHeaders(newBlock.Header) + callInsertBlockHook(newBlock) }); err != nil { i.logger.Error("failed to bulk sync", "err", err) @@ -540,7 +573,7 @@ func (i *Ibft) runSyncState() { // we can just move ahead if i.isValidSnapshot() { i.startNewSequence() - i.setState(AcceptState) + i.setState(currentstate.AcceptState) continue } @@ -551,10 +584,9 @@ func (i *Ibft) runSyncState() { i.syncer.WatchSyncWithPeer(p, func(newBlock *types.Block) bool { // After each written block, update the snapshot store for PoS. // The snapshot store is currently updated for PoA inside the ProcessHeadersHook - callInsertBlockHook(newBlock.Number()) + callInsertBlockHook(newBlock) i.syncer.Broadcast(newBlock) - i.txpool.ResetWithHeaders(newBlock.Header) isValidator = i.isValidSnapshot() return isValidator @@ -565,7 +597,7 @@ func (i *Ibft) runSyncState() { // and we are a validator of that chain so we need to change to AcceptState // so that we can start to do some stuff there i.startNewSequence() - i.setState(AcceptState) + i.setState(currentstate.AcceptState) } } @@ -577,10 +609,21 @@ func (i *Ibft) runSyncState() { // unlock current block if new blocks are added if endingHeight > beginningHeight { - i.state.unlock() + i.state.Unlock() } } +// shouldWriteSystemTransactions checks whether system contract transaction should write at given height +// +// only active after detroit hardfork +func (i *Ibft) shouldWriteSystemTransactions(height uint64) bool { + if i.config == nil || i.config.Params == nil || i.config.Params.Forks == nil { // old logic test + return false + } + + return i.config.Params.Forks.At(height).Detroit +} + // shouldWriteTransactions checks if each consensus mechanism accepts a block with transactions at given height // returns true if all mechanisms accept // otherwise return false @@ -615,6 +658,7 @@ func (i *Ibft) buildBlock(snap *Snapshot, parent *types.Header) (*types.Block, e return nil, err } + // update gas limit first header.GasLimit = gasLimit if hookErr := i.runHook(CandidateVoteHook, header.Number, &candidateVoteHookParams{ @@ -624,12 +668,14 @@ func (i *Ibft) buildBlock(snap *Snapshot, parent *types.Header) (*types.Block, e i.logger.Error(fmt.Sprintf("Unable to run hook %s, %v", CandidateVoteHook, hookErr)) } - // set the timestamp + // set the brocasting timestamp if possible + // must use parent timestamp, parentTime := time.Unix(int64(parent.Timestamp), 0) headerTime := parentTime.Add(i.blockTime) + now := time.Now() - if headerTime.Before(time.Now()) { - headerTime = time.Now() + if headerTime.Before(now) { + headerTime = now } header.Timestamp = uint64(headerTime.Unix()) @@ -642,31 +688,79 @@ func (i *Ibft) buildBlock(snap *Snapshot, parent *types.Header) (*types.Block, e return nil, err } + // upgrade system if needed + upgrader.UpgradeSystem( + i.config.Params.ChainID, + i.config.Params.Forks, + header.Number, + transition.Txn(), + i.logger, + ) + // If the mechanism is PoS -> build a regular block if it's not an end-of-epoch block // If the mechanism is PoA -> always build a regular block, regardless of epoch var ( - txs []*types.Transaction - dropTxs []*types.Transaction - resetTxs []*demoteTransaction + txs []*types.Transaction + includedTxs []*types.Transaction + dropTxs []*types.Transaction + resetTxs []*demoteTransaction ) + // insert normal transactions if i.shouldWriteTransactions(header.Number) { - txs, dropTxs, resetTxs = i.writeTransactions(gasLimit, transition, headerTime.Add(i.blockTime)) + includedTxs, dropTxs, resetTxs = i.writeTransactions(gasLimit, transition, headerTime.Add(i.blockTime)) + txs = append(txs, includedTxs...) + } + + // insert system transactions at last to ensure it works + if i.shouldWriteSystemTransactions(header.Number) { + txn := transition.Txn() + + // make slash tx if needed + if i.currentRound() > 0 { + // only punish the first validator + lastBlockProposer, _ := ecrecoverFromHeader(parent) + + needPunished := i.state.CalcNeedPunished(i.currentRound(), lastBlockProposer) + if len(needPunished) > 0 { + tx, err := i.makeTransitionSlashTx(txn, header.Number, needPunished[0]) + if err != nil { + return nil, err + } + + // system transaction, increase gas limit if needed + increaseHeaderGasIfNeeded(transition, header, tx) + + // execute slash tx + if err := transition.Write(tx); err != nil { + return nil, err + } + + txs = append(txs, tx) + } + } + + // make deposit tx + tx, err := i.makeTransitionDepositTx(transition.Txn(), header.Number) + if err != nil { + return nil, err + } + + // system transaction, increase gas limit if needed + increaseHeaderGasIfNeeded(transition, header, tx) + + // execute deposit tx + if err := transition.Write(tx); err != nil { + return nil, err + } + + txs = append(txs, tx) } if err := i.PreStateCommit(header, transition); err != nil { return nil, err } - // upgrade system if needed - upgrader.UpgradeSystem( - i.config.Params.ChainID, - i.config.Params.Forks, - header.Number, - transition.Txn(), - i.logger, - ) - _, root := transition.Commit() header.StateRoot = root header.GasUsed = transition.TotalGas() @@ -710,6 +804,97 @@ func (i *Ibft) buildBlock(snap *Snapshot, parent *types.Header) (*types.Block, e return block, nil } +func increaseHeaderGasIfNeeded(transition *state.Transition, header *types.Header, tx *types.Transaction) { + if transition.TotalGas()+tx.Gas <= header.GasLimit { + return + } + + extractAmount := transition.TotalGas() + tx.Gas - header.GasLimit + + // increase it + header.GasLimit += extractAmount + transition.IncreaseSystemTransactionGas(extractAmount) +} + +func (i *Ibft) currentRound() uint64 { + return i.state.Round() +} + +func (i *Ibft) makeTransitionDepositTx( + txn *state.Txn, + height uint64, +) (*types.Transaction, error) { + // singer + signer := i.getSigner(height) + + // make deposit tx + tx, err := validatorset.MakeDepositTx(txn, i.validatorKeyAddr) + if err != nil { + return nil, err + } + + // sign tx + tx, err = signer.SignTx(tx, i.validatorKey) + if err != nil { + return nil, err + } + + return tx, err +} + +func (i *Ibft) makeTransitionSlashTx( + txn *state.Txn, + height uint64, + needPunished types.Address, +) (*types.Transaction, error) { + // singer + signer := i.getSigner(height) + + // make deposit tx + tx, err := validatorset.MakeSlashTx(txn, i.validatorKeyAddr, needPunished) + if err != nil { + return nil, err + } + + // sign tx + tx, err = signer.SignTx(tx, i.validatorKey) + if err != nil { + return nil, err + } + + return tx, err +} + +func (i *Ibft) isActiveValidator(addr types.Address) bool { + return i.currentValidators.Includes(addr) +} + +// updateCurrentModules updates Txsigner and Validators +// that are used at specified height +func (i *Ibft) updateCurrentModules(height uint64) error { + snap, err := i.getSnapshot(height) + if err != nil { + return err + } + + i.currentValidators = snap.Set + i.currentTxSigner = i.getSigner(height) + + i.logger.Info("update current module", + "height", height, + "validators", i.currentValidators, + ) + + return nil +} + +func (i *Ibft) getSigner(height uint64) crypto.TxSigner { + return crypto.NewSigner( + i.config.Params.Forks.At(height), + uint64(i.config.Params.ChainID), + ) +} + type transitionInterface interface { Write(txn *types.Transaction) error WriteFailedReceipt(txn *types.Transaction) error @@ -879,26 +1064,35 @@ func (i *Ibft) banishLongTimeConsumingTx(tx *types.Transaction, begin time.Time) func (i *Ibft) runAcceptState() { // start new round // set log output logger := i.logger.Named("acceptState") - logger.Info("Accept state", "sequence", i.state.view.Sequence, "round", i.state.view.Round+1) + logger.Info("Accept state", "sequence", i.state.Sequence(), "round", i.state.Round()+1) // set consensus_rounds metric output - i.metrics.Rounds.Set(float64(i.state.view.Round + 1)) + i.metrics.Rounds.Set(float64(i.state.Round() + 1)) // This is the state in which we either propose a block or wait for the pre-prepare message parent := i.blockchain.Header() number := parent.Number + 1 - if number != i.state.view.Sequence { - i.logger.Error("sequence not correct", "parent", parent.Number, "sequence", i.state.view.Sequence) - i.setState(SyncState) + if number != i.state.Sequence() { + i.logger.Error("sequence not correct", "parent", parent.Number, "sequence", i.state.Sequence()) + i.setState(currentstate.SyncState) return } + // update current module cache + if err := i.updateCurrentModules(number); err != nil { + i.logger.Error( + "failed to update submodules", + "height", number, + "err", err, + ) + } + snap, err := i.getSnapshot(parent.Number) if err != nil { i.logger.Error("cannot find snapshot", "num", parent.Number) - i.setState(SyncState) + i.setState(currentstate.SyncState) return } @@ -906,21 +1100,21 @@ func (i *Ibft) runAcceptState() { // start new round if !snap.Set.Includes(i.validatorKeyAddr) { // we are not a validator anymore, move back to sync state i.logger.Info("we are not a validator anymore") - i.setState(SyncState) + i.setState(currentstate.SyncState) return } - if hookErr := i.runHook(AcceptStateLogHook, i.state.view.Sequence, snap); hookErr != nil { + if hookErr := i.runHook(AcceptStateLogHook, i.state.Sequence(), snap); hookErr != nil { i.logger.Error(fmt.Sprintf("Unable to run hook %s, %v", AcceptStateLogHook, hookErr)) } - i.state.validators = snap.Set + i.state.SetValidators(snap.Set) //Update the No.of validator metric i.metrics.Validators.Set(float64(len(snap.Set))) // reset round messages - i.state.resetRoundMsgs() + i.state.ResetRoundMsgs() // select the proposer of the block var lastProposer types.Address @@ -928,25 +1122,27 @@ func (i *Ibft) runAcceptState() { // start new round lastProposer, _ = ecrecoverFromHeader(parent) } - if hookErr := i.runHook(CalculateProposerHook, i.state.view.Sequence, lastProposer); hookErr != nil { + if hookErr := i.runHook(CalculateProposerHook, i.state.Sequence(), lastProposer); hookErr != nil { i.logger.Error(fmt.Sprintf("Unable to run hook %s, %v", CalculateProposerHook, hookErr)) } - if i.state.proposer == i.validatorKeyAddr { + if i.state.Proposer() == i.validatorKeyAddr { logger.Info("we are the proposer", "block", number) - if !i.state.locked { + if !i.state.IsLocked() { // since the state is not locked, we need to build a new block - i.state.block, err = i.buildBlock(snap, parent) + block, err := i.buildBlock(snap, parent) if err != nil { i.logger.Error("failed to build block", "err", err) - i.setState(RoundChangeState) + i.setState(currentstate.RoundChangeState) return } + i.state.SetBlock(block) + // calculate how much time do we have to wait to mine the block - delay := time.Until(time.Unix(int64(i.state.block.Header.Timestamp), 0)) + delay := time.Until(time.Unix(int64(block.Header.Timestamp), 0)) delayTimer := time.NewTimer(delay) @@ -964,30 +1160,30 @@ func (i *Ibft) runAcceptState() { // start new round i.sendPrepareMsg() // move to validation state for new prepare messages - i.setState(ValidateState) + i.setState(currentstate.ValidateState) return } - i.logger.Info("proposer calculated", "proposer", i.state.proposer, "block", number) + i.logger.Info("proposer calculated", "proposer", i.state.Proposer(), "block", number) // we are NOT a proposer for the block. Then, we have to wait // for a pre-prepare message from the proposer - timeout := exponentialTimeout(i.state.view.Round) - for i.getState() == AcceptState { + timeout := i.state.MessageTimeout() + for i.getState() == currentstate.AcceptState { msg, ok := i.getNextMessage(timeout) if !ok { return } if msg == nil { - i.setState(RoundChangeState) + i.setState(currentstate.RoundChangeState) continue } - if msg.From != i.state.proposer.String() { + if msg.From != i.state.Proposer().String() { i.logger.Error("msg received from wrong proposer") continue @@ -1004,25 +1200,25 @@ func (i *Ibft) runAcceptState() { // start new round block := &types.Block{} if err := block.UnmarshalRLP(msg.Proposal.Value); err != nil { i.logger.Error("failed to unmarshal block", "err", err) - i.setState(RoundChangeState) + i.setState(currentstate.RoundChangeState) return } // Make sure the proposing block height match the current sequence - if block.Number() != i.state.view.Sequence { - i.logger.Error("sequence not correct", "block", block.Number, "sequence", i.state.view.Sequence) + if block.Number() != i.state.Sequence() { + i.logger.Error("sequence not correct", "block", block.Number, "sequence", i.state.Sequence()) i.handleStateErr(errIncorrectBlockHeight) return } - if i.state.locked { + if i.state.IsLocked() { // the state is locked, we need to receive the same block - if block.Hash() == i.state.block.Hash() { + if block.Hash() == i.state.Block().Hash() { // fast-track and send a commit message and wait for validations i.sendCommitMsg() - i.setState(ValidateState) + i.setState(currentstate.ValidateState) } else { i.handleStateErr(errIncorrectBlockLocked) } @@ -1054,14 +1250,58 @@ func (i *Ibft) runAcceptState() { // start new round continue } - i.state.block = block + i.state.SetBlock(block) // send prepare message and wait for validations i.sendPrepareMsg() - i.setState(ValidateState) + i.setState(currentstate.ValidateState) } } } +func (i *Ibft) isDepositTx(height uint64, coinbase types.Address, tx *types.Transaction) bool { + if tx.To == nil || *tx.To != systemcontracts.AddrValidatorSetContract { + return false + } + + // check input + if !validatorset.IsDepositTransactionSignture(tx.Input) { + return false + } + + // signer by height + signer := i.getSigner(height) + + // tx sender + from, err := signer.Sender(tx) + if err != nil { + return false + } + + return from == coinbase +} + +func (i *Ibft) isSlashTx(height uint64, coinbase types.Address, tx *types.Transaction) bool { + if tx.To == nil || *tx.To != systemcontracts.AddrValidatorSetContract { + return false + } + + // check input + if !validatorset.IsSlashTransactionSignture(tx.Input) { + return false + } + + // signer by height + signer := i.getSigner(height) + + // tx sender + from, err := signer.Sender(tx) + if err != nil { + return false + } + + return from == coinbase +} + // runValidateState implements the Validate state loop. // // The Validate state is rather simple - all nodes do in this state is read messages @@ -1071,7 +1311,7 @@ func (i *Ibft) runValidateState() { sendCommit := func() { // at this point either we have enough prepare messages // or commit messages so we can lock the block - i.state.lock() + i.state.Lock() if !hasCommitted { // send the commit message @@ -1081,8 +1321,8 @@ func (i *Ibft) runValidateState() { } } - timeout := exponentialTimeout(i.state.view.Round) - for i.getState() == ValidateState { + timeout := i.state.MessageTimeout() + for i.getState() == currentstate.ValidateState { msg, ok := i.getNextMessage(timeout) if !ok { // closing @@ -1091,9 +1331,9 @@ func (i *Ibft) runValidateState() { if msg == nil { i.logger.Debug("ValidateState got message timeout, should change round", - "sequence", i.state.view.Sequence, "round", i.state.view.Round+1) - i.state.unlock() - i.setState(RoundChangeState) + "sequence", i.state.Sequence(), "round", i.state.Round()+1) + i.state.Unlock() + i.setState(currentstate.RoundChangeState) continue } @@ -1106,10 +1346,10 @@ func (i *Ibft) runValidateState() { } // check msg number and round, might from some faulty nodes - if i.state.view.Sequence != msg.View.GetSequence() || - i.state.view.Round != msg.View.GetRound() { + if i.state.Sequence() != msg.View.GetSequence() || + i.state.Round() != msg.View.GetRound() { i.logger.Debug("ValidateState got message not matching sequence and round", - "my-sequence", i.state.view.Sequence, "my-round", i.state.view.Round+1, + "my-sequence", i.state.Sequence(), "my-round", i.state.Round()+1, "other-sequence", msg.View.GetSequence(), "other-round", msg.View.GetRound()) continue @@ -1117,34 +1357,34 @@ func (i *Ibft) runValidateState() { switch msg.Type { case proto.MessageReq_Prepare: - i.state.addPrepared(msg) + i.state.AddPrepared(msg) case proto.MessageReq_Commit: - i.state.addCommitted(msg) + i.state.AddCommitted(msg) default: i.logger.Error("BUG: %s, validate state don't not handle type.msg: %d", reflect.TypeOf(msg.Type), msg.Type) } - if i.state.numPrepared() > i.state.NumValid() { + if i.state.NumPrepared() > i.state.NumValid() { // we have received enough pre-prepare messages sendCommit() } - if i.state.numCommitted() > i.state.NumValid() { + if i.state.NumCommitted() > i.state.NumValid() { // we have received enough commit messages sendCommit() // try to commit the block (TODO: just to get out of the loop) - i.setState(CommitState) + i.setState(currentstate.CommitState) } } - if i.getState() == CommitState { + if i.getState() == currentstate.CommitState { // at this point either if it works or not we need to unlock - block := i.state.block - i.state.unlock() + block := i.state.Block() + i.state.Unlock() if err := i.insertBlock(block); err != nil { // start a new round with the state unlocked since we need to @@ -1159,7 +1399,7 @@ func (i *Ibft) runValidateState() { i.startNewSequence() // move ahead to the next block - i.setState(AcceptState) + i.setState(currentstate.AcceptState) } } } @@ -1182,11 +1422,12 @@ func (i *Ibft) updateMetrics(block *types.Block) { //Update the Number of transactions in the block metric i.metrics.NumTxs.Set(float64(len(block.Body().Transactions))) } + func (i *Ibft) insertBlock(block *types.Block) error { // Gather the committed seals for the block committedSeals := make([][]byte, 0) - for _, commit := range i.state.committed { + for _, commit := range i.state.Committed() { // no need to check the format of seal here because writeCommittedSeals will check seal, err := hex.DecodeHex(commit.Seal) if err != nil { @@ -1228,11 +1469,11 @@ func (i *Ibft) insertBlock(block *types.Block) error { i.logger.Info( "block committed", - "sequence", i.state.view.Sequence, + "sequence", i.state.Sequence(), "hash", block.Hash(), - "validators", len(i.state.validators), - "rounds", i.state.view.Round+1, - "committed", i.state.numCommitted(), + "validators", len(i.state.Validators()), + "rounds", i.state.Round()+1, + "committed", i.state.NumCommitted(), ) // broadcast the new block @@ -1253,8 +1494,8 @@ var ( ) func (i *Ibft) handleStateErr(err error) { - i.state.err = err - i.setState(RoundChangeState) + i.state.HandleErr(err) + i.setState(currentstate.RoundChangeState) } func (i *Ibft) runRoundChangeState() { @@ -1264,12 +1505,12 @@ func (i *Ibft) runRoundChangeState() { i.startNewRound(round) i.metrics.Rounds.Set(float64(round)) // clean the round - i.state.cleanRound(round) + i.state.CleanRound(round) // send the round change message i.sendRoundChange() } sendNextRoundChange := func() { - sendRoundChange(i.state.view.Round + 1) + sendRoundChange(i.state.NextRound()) } checkTimeout := func() { @@ -1279,9 +1520,9 @@ func (i *Ibft) runRoundChangeState() { if bestPeer != nil { lastProposal := i.blockchain.Header() if bestPeer.Number() > lastProposal.Number { - i.logger.Debug("it has found a better peer to connect", "local", lastProposal.Number, "remote", bestPeer.Number()) + i.logger.Info("it has found a better peer to connect", "local", lastProposal.Number, "remote", bestPeer.Number()) // we need to catch up with the last sequence - i.setState(SyncState) + i.setState(currentstate.SyncState) return } @@ -1295,13 +1536,13 @@ func (i *Ibft) runRoundChangeState() { // if the round was triggered due to an error, we send our own // next round change - if err := i.state.getErr(); err != nil { + if err := i.state.ConsumeErr(); err != nil { i.logger.Debug("round change handle err", "err", err) sendNextRoundChange() } else { // otherwise, it is due to a timeout in any stage // First, we try to sync up with any max round already available - if maxRound, ok := i.state.maxRound(); ok { + if maxRound, ok := i.state.MaxRound(); ok { i.logger.Debug("round change set max round", "round", maxRound) sendRoundChange(maxRound) } else { @@ -1311,8 +1552,9 @@ func (i *Ibft) runRoundChangeState() { } // create a timer for the round change - timeout := exponentialTimeout(i.state.view.Round) - for i.getState() == RoundChangeState { + for i.getState() == currentstate.RoundChangeState { + // timeout should update every time it enters a new round + timeout := i.state.MessageTimeout() msg, ok := i.getNextMessage(timeout) if !ok { // closing @@ -1320,10 +1562,8 @@ func (i *Ibft) runRoundChangeState() { } if msg == nil { - i.logger.Debug("round change timeout") + i.logger.Info("round change timeout") checkTimeout() - // update the timeout duration - timeout = exponentialTimeout(i.state.view.Round) continue } @@ -1341,12 +1581,10 @@ func (i *Ibft) runRoundChangeState() { if num == i.state.NumValid() { // start a new round immediately i.startNewRound(msg.View.Round) - i.setState(AcceptState) - } else if num == i.state.validators.MaxFaultyNodes()+1 { + i.setState(currentstate.AcceptState) + } else if num == i.state.MaxFaultyNodes()+1 { // weak certificate, try to catch up if our round number is smaller - if i.state.view.Round < msg.View.Round { - // update timer - timeout = exponentialTimeout(i.state.view.Round) + if i.state.Round() < msg.View.Round { sendRoundChange(msg.View.Round) } } @@ -1377,18 +1615,18 @@ func (i *Ibft) gossip(typ proto.MessageReq_Type) { } // add View - msg.View = i.state.view.Copy() + msg.View = i.state.View().Copy() // if we are sending a preprepare message we need to include the proposed block if msg.Type == proto.MessageReq_Preprepare { msg.Proposal = &anypb.Any{ - Value: i.state.block.MarshalRLP(), + Value: i.state.Block().MarshalRLP(), } } // if the message is commit, we need to add the committed seal if msg.Type == proto.MessageReq_Commit { - seal, err := writeCommittedSeal(i.validatorKey, i.state.block.Header) + seal, err := writeCommittedSeal(i.validatorKey, i.state.Block().Header) if err != nil { i.logger.Error("failed to commit seal", "err", err) @@ -1417,19 +1655,19 @@ func (i *Ibft) gossip(typ proto.MessageReq_Type) { } // getState returns the current IBFT state -func (i *Ibft) getState() IbftState { - return i.state.getState() +func (i *Ibft) getState() currentstate.IbftState { + return i.state.GetState() } // isState checks if the node is in the passed in state -func (i *Ibft) isState(s IbftState) bool { - return i.state.getState() == s +func (i *Ibft) isState(s currentstate.IbftState) bool { + return i.state.GetState() == s } // setState sets the IBFT state -func (i *Ibft) setState(s IbftState) { +func (i *Ibft) setState(s currentstate.IbftState) { i.logger.Info("state change", "new", s) - i.state.setState(s) + i.state.SetState(s) } // forceTimeout sets the forceTimeoutCh flag to true @@ -1451,7 +1689,7 @@ func (i *Ibft) verifyHeaderImpl(snap *Snapshot, parent, header *types.Header) er } // ensure validatorset exists in extra data if len(extract.Validators) == 0 { - return fmt.Errorf("empty extract validatorset") + return ErrEmptyValidatorExtract } if hookErr := i.runHook(VerifyHeadersHook, header.Number, header.Nonce); hookErr != nil { @@ -1459,16 +1697,34 @@ func (i *Ibft) verifyHeaderImpl(snap *Snapshot, parent, header *types.Header) er } if header.MixHash != IstanbulDigest { - return fmt.Errorf("invalid mixhash") + return ErrInvalidMixHash } if header.Sha3Uncles != types.EmptyUncleHash { - return fmt.Errorf("invalid sha3 uncles") + return ErrInvalidUncleHash } // difficulty has to match number if header.Difficulty != header.Number { - return fmt.Errorf("wrong difficulty") + return ErrWrongDifficulty + } + + // check timestamp + if i.shouldVerifyTimestamp(header.Number) { + // The diff between block timestamp and 'now' should not exceeds timeout. + // Timestamp ascending array [parentTs, blockTs, now+blockTimeout] + before, after := parent.Timestamp, uint64(time.Now().Add(i.blockTime).Unix()) + + // header timestamp should not goes back + if header.Timestamp <= before || header.Timestamp > after { + i.logger.Debug("future blocktime invalid", + "before", before, + "after", after, + "current", header.Timestamp, + ) + + return ErrInvalidBlockTimestamp + } } // verify the sealer @@ -1479,6 +1735,18 @@ func (i *Ibft) verifyHeaderImpl(snap *Snapshot, parent, header *types.Header) er return nil } +// shouldVerifyTimeStamp checks whether block timestamp should be verified or not +// +// only active after detroit hardfork +func (i *Ibft) shouldVerifyTimestamp(height uint64) bool { + // backward compatible + if i.config == nil || i.config.Params == nil || i.config.Params.Forks == nil { + return false + } + + return i.config.Params.Forks.IsDetroit(height) +} + // VerifyHeader wrapper for verifying headers func (i *Ibft) VerifyHeader(header *types.Header) error { parent, ok := i.blockchain.GetHeaderByNumber(header.Number - 1) @@ -1529,6 +1797,19 @@ func (i *Ibft) PreStateCommit(header *types.Header, txn *state.Transition) error return nil } +func (i *Ibft) IsSystemTransaction(height uint64, coinbase types.Address, tx *types.Transaction) bool { + // only active after detroit hardfork + if !i.shouldWriteSystemTransactions(height) { + return false + } + + if i.isDepositTx(height, coinbase, tx) { + return true + } + + return i.isSlashTx(height, coinbase, tx) +} + // GetEpoch returns the current epoch func (i *Ibft) GetEpoch(number uint64) uint64 { if number%i.epochSize == 0 { @@ -1573,7 +1854,7 @@ func (i *Ibft) getNextMessage(timeout time.Duration) (*proto.MessageReq, bool) { timeoutCh := time.NewTimer(timeout) for { - msg := i.msgQueue.readMessage(i.getState(), i.state.view) + msg := i.msgQueue.readMessage(i.getState(), i.state.View()) if msg != nil { return msg.obj, true } @@ -1617,16 +1898,16 @@ func (i *Ibft) pushMessage(msg *proto.MessageReq) { func (i *Ibft) startNewSequence() { header := i.blockchain.Header() - i.state.view = &proto.View{ + i.state.SetView(&proto.View{ Sequence: header.Number + 1, Round: 0, - } + }) } // startNewRound changes the round in the view of state func (i *Ibft) startNewRound(newRound uint64) { - i.state.view = &proto.View{ - Sequence: i.state.view.Sequence, + i.state.SetView(&proto.View{ + Sequence: i.state.Sequence(), Round: newRound, - } + }) } diff --git a/consensus/ibft/ibft_test.go b/consensus/ibft/ibft_test.go index b84aecb545..02ade275c1 100644 --- a/consensus/ibft/ibft_test.go +++ b/consensus/ibft/ibft_test.go @@ -9,7 +9,9 @@ import ( "time" "github.com/dogechain-lab/dogechain/blockchain" + "github.com/dogechain-lab/dogechain/chain" "github.com/dogechain-lab/dogechain/consensus" + "github.com/dogechain-lab/dogechain/consensus/ibft/currentstate" "github.com/dogechain-lab/dogechain/consensus/ibft/proto" "github.com/dogechain-lab/dogechain/helper/common" "github.com/dogechain-lab/dogechain/helper/hex" @@ -228,7 +230,7 @@ func TestTransition_ValidateState_Prepare(t *testing.T) { // we receive enough prepare messages to lock and commit the block i := newMockIbft(t, []string{"A", "B", "C", "D"}, "A") - i.setState(ValidateState) + i.setState(currentstate.ValidateState) i.emitMsg(&proto.MessageReq{ From: "A", @@ -257,7 +259,7 @@ func TestTransition_ValidateState_Prepare(t *testing.T) { i.expect(expectResult{ sequence: 1, - state: ValidateState, + state: currentstate.ValidateState, prepareMsgs: 3, commitMsgs: 1, // A commit message locked: true, @@ -274,10 +276,10 @@ func TestTransition_ValidateState_CommitFastTrack(t *testing.T) { seal := hex.EncodeToHex(make([]byte, IstanbulExtraSeal)) - i.setState(ValidateState) - i.state.view = proto.ViewMsg(1, 0) - i.state.block = i.DummyBlock() - i.state.locked = true + i.setState(currentstate.ValidateState) + // i.state.SetView(proto.ViewMsg(1, 0)) + i.state.SetBlock(i.DummyBlock()) + i.state.Lock() i.emitMsg(&proto.MessageReq{ From: "A", @@ -318,7 +320,7 @@ func TestTransition_AcceptState_ToSync(t *testing.T) { // we are in AcceptState and we are not in the validators list // means that we have been removed as validator, move to sync state i := newMockIbft(t, []string{"A", "B", "C", "D"}, "") - i.setState(AcceptState) + i.setState(currentstate.AcceptState) i.Close() // we are the proposer and we need to build a block @@ -326,40 +328,40 @@ func TestTransition_AcceptState_ToSync(t *testing.T) { i.expect(expectResult{ sequence: 1, - state: SyncState, + state: currentstate.SyncState, }) } func TestTransition_AcceptState_Proposer_Locked(t *testing.T) { // If we are the proposer and there is a lock value we need to propose it i := newMockIbft(t, []string{"A", "B", "C", "D"}, "A") - i.setState(AcceptState) + i.setState(currentstate.AcceptState) - i.state.locked = true - i.state.block = &types.Block{ + i.state.Lock() + i.state.SetBlock(&types.Block{ Header: &types.Header{ Number: 10, }, - } + }) i.runCycle() i.expect(expectResult{ sequence: 1, - state: ValidateState, + state: currentstate.ValidateState, locked: true, outgoing: 2, // preprepare and prepare }) - if i.state.block.Number() != 10 { + if i.state.Block().Number() != 10 { t.Fatal("bad block") } } func TestTransition_AcceptState_Validator_VerifyCorrect(t *testing.T) { i := newMockIbft(t, []string{"A", "B", "C"}, "B") - i.state.view = proto.ViewMsg(1, 0) - i.setState(AcceptState) + i.state.SetView(proto.ViewMsg(1, 0)) + i.setState(currentstate.AcceptState) block := i.DummyBlock() header, err := writeSeal(i.pool.get("A").priv, block.Header) @@ -382,15 +384,15 @@ func TestTransition_AcceptState_Validator_VerifyCorrect(t *testing.T) { i.expect(expectResult{ sequence: 1, - state: ValidateState, + state: currentstate.ValidateState, outgoing: 1, // prepare }) } func TestTransition_AcceptState_Validator_VerifyFails(t *testing.T) { i := newMockIbft(t, []string{"A", "B", "C"}, "B") - i.state.view = proto.ViewMsg(1, 0) - i.setState(AcceptState) + i.state.SetView(proto.ViewMsg(1, 0)) + i.setState(currentstate.AcceptState) block := i.DummyBlock() block.Header.MixHash = types.Hash{} // invalidates the block @@ -415,15 +417,15 @@ func TestTransition_AcceptState_Validator_VerifyFails(t *testing.T) { i.expect(expectResult{ sequence: 1, - state: RoundChangeState, + state: currentstate.RoundChangeState, err: errBlockVerificationFailed, }) } func TestTransition_AcceptState_Validator_ProposerInvalid(t *testing.T) { i := newMockIbft(t, []string{"A", "B", "C"}, "B") - i.state.view = proto.ViewMsg(1, 0) - i.setState(AcceptState) + i.state.SetView(proto.ViewMsg(1, 0)) + i.setState(currentstate.AcceptState) // A is the proposer but C sends the propose, we do not fail // but wait for timeout to move to roundChange state @@ -441,22 +443,22 @@ func TestTransition_AcceptState_Validator_ProposerInvalid(t *testing.T) { i.expect(expectResult{ sequence: 1, - state: RoundChangeState, + state: currentstate.RoundChangeState, }) } func TestTransition_AcceptState_Validator_LockWrong(t *testing.T) { i := newMockIbft(t, []string{"A", "B", "C"}, "B") - i.state.view = proto.ViewMsg(1, 0) - i.setState(AcceptState) + i.state.SetView(proto.ViewMsg(1, 0)) + i.setState(currentstate.AcceptState) // locked block block := i.DummyBlock() block.Header.Number = 1 block.Header.ComputeHash() - i.state.block = block - i.state.locked = true + i.state.SetBlock(block) + i.state.Lock() // proposed block block1 := i.DummyBlock() @@ -476,7 +478,7 @@ func TestTransition_AcceptState_Validator_LockWrong(t *testing.T) { i.expect(expectResult{ sequence: 1, - state: RoundChangeState, + state: currentstate.RoundChangeState, locked: true, err: errIncorrectBlockHeight, }) @@ -484,16 +486,16 @@ func TestTransition_AcceptState_Validator_LockWrong(t *testing.T) { func TestTransition_AcceptState_Validator_LockCorrect(t *testing.T) { i := newMockIbft(t, []string{"A", "B", "C"}, "B") - i.state.view = proto.ViewMsg(1, 0) - i.setState(AcceptState) + i.state.SetView(proto.ViewMsg(1, 0)) + i.setState(currentstate.AcceptState) // locked block block := i.DummyBlock() block.Header.Number = 1 block.Header.ComputeHash() - i.state.block = block - i.state.locked = true + i.state.SetBlock(block) + i.state.Lock() i.emitMsg(&proto.MessageReq{ From: "A", @@ -508,7 +510,7 @@ func TestTransition_AcceptState_Validator_LockCorrect(t *testing.T) { i.expect(expectResult{ sequence: 1, - state: ValidateState, + state: currentstate.ValidateState, locked: true, outgoing: 1, // prepare message }) @@ -539,8 +541,8 @@ func TestTransition_AcceptState_Reject_WrongHeight_Block(t *testing.T) { proposedBlock = blockchain.MockBlock(proposeBlockHeight, types.ZeroHash, pool.get("C").priv, pool.ValidatorSet()) ) - i.state.view = proto.ViewMsg(nextSequence, 0) - i.setState(AcceptState) + i.state.SetView(proto.ViewMsg(nextSequence, 0)) + i.setState(currentstate.AcceptState) blockchain.HeaderHandler = func() *types.Header { return latestBlock.Header @@ -560,7 +562,7 @@ func TestTransition_AcceptState_Reject_WrongHeight_Block(t *testing.T) { i.expect(expectResult{ sequence: nextSequence, - state: RoundChangeState, + state: currentstate.RoundChangeState, locked: false, outgoing: 0, err: errIncorrectBlockHeight, @@ -569,7 +571,7 @@ func TestTransition_AcceptState_Reject_WrongHeight_Block(t *testing.T) { func TestTransition_RoundChangeState_CatchupRound(t *testing.T) { m := newMockIbft(t, []string{"A", "B", "C", "D"}, "A") - m.setState(RoundChangeState) + m.setState(currentstate.RoundChangeState) // new messages arrive with round number 2 m.emitMsg(&proto.MessageReq{ @@ -599,7 +601,7 @@ func TestTransition_RoundChangeState_CatchupRound(t *testing.T) { sequence: 1, round: 2, outgoing: 1, // our new round change - state: AcceptState, + state: currentstate.AcceptState, }) } @@ -607,7 +609,7 @@ func TestTransition_RoundChangeState_Timeout(t *testing.T) { m := newMockIbft(t, []string{"A", "B", "C", "D"}, "A") m.forceTimeout() - m.setState(RoundChangeState) + m.setState(currentstate.RoundChangeState) m.Close() // increases to round 1 at the beginning of the round and sends @@ -620,13 +622,13 @@ func TestTransition_RoundChangeState_Timeout(t *testing.T) { sequence: 1, round: 2, outgoing: 2, // two round change messages - state: RoundChangeState, + state: currentstate.RoundChangeState, }) } func TestTransition_RoundChangeState_WeakCertificate(t *testing.T) { m := newMockIbft(t, []string{"A", "B", "C", "D", "E", "F", "G"}, "A") - m.setState(RoundChangeState) + m.setState(currentstate.RoundChangeState) // send three roundChange messages which are enough to force a // weak change where the client moves to that new round state @@ -653,7 +655,7 @@ func TestTransition_RoundChangeState_WeakCertificate(t *testing.T) { sequence: 1, round: 2, outgoing: 2, // two round change messages (0->1, 1->2 after weak certificate) - state: RoundChangeState, + state: currentstate.RoundChangeState, }) } @@ -663,15 +665,15 @@ func TestTransition_RoundChangeState_ErrStartNewRound(t *testing.T) { m := newMockIbft(t, []string{"A", "B"}, "A") m.Close() - m.state.err = errBlockVerificationFailed + m.state.HandleErr(errBlockVerificationFailed) - m.setState(RoundChangeState) + m.setState(currentstate.RoundChangeState) m.runCycle() m.expect(expectResult{ sequence: 1, round: 1, - state: RoundChangeState, + state: currentstate.RoundChangeState, outgoing: 1, }) } @@ -682,15 +684,15 @@ func TestTransition_RoundChangeState_StartNewRound(t *testing.T) { m := newMockIbft(t, []string{"A", "B"}, "A") m.Close() - m.state.view.Sequence = 1 + m.state.SetView(proto.ViewMsg(1, 0)) - m.setState(RoundChangeState) + m.setState(currentstate.RoundChangeState) m.runCycle() m.expect(expectResult{ sequence: 1, round: 1, - state: RoundChangeState, + state: currentstate.RoundChangeState, outgoing: 1, }) } @@ -710,13 +712,13 @@ func TestTransition_RoundChangeState_MaxRound(t *testing.T) { }, }) - m.setState(RoundChangeState) + m.setState(currentstate.RoundChangeState) m.runCycle() m.expect(expectResult{ sequence: 1, round: 10, - state: RoundChangeState, + state: currentstate.RoundChangeState, outgoing: 1, }) } @@ -828,6 +830,26 @@ func TestIBFT_WriteTransactions(t *testing.T) { expectedDemoteTxnsCount: 0, }, }, + // { + // "transaction whose gas exceeds block gas limit is included but with failedReceipt", + // testParams{ + // txns: []*types.Transaction{ + // {Nonce: 1}, // recoverable - returned to pool + // {Nonce: 2}, // unrecoverable + // {Nonce: 3}, // included + // {Nonce: 4, Gas: 10001}, // exceeds block gas limit, included with failed receipt + // {Nonce: 5}, // gas limit reach, skip + // {Nonce: 6}, // included when in mock + // {Nonce: 7}, // included when in mock + // }, + // recoverableTxnsIndexes: []int{0}, + // unrecoverableTxnsIndexes: []int{1}, + // gasLimitReachedTxnIndex: 4, + // expectedTxPoolLength: 1, + // expectedIncludedTxnsCount: 4, // nonce: 3,4,6,7 + // expectedFailReceiptsWritten: 1, + // }, + // }, } for _, test := range testCases { @@ -850,7 +872,7 @@ func TestIBFT_WriteTransactions(t *testing.T) { func TestRunSyncState_NewHeadReceivedFromPeer_CallsTxPoolResetWithHeaders(t *testing.T) { m := newMockIbft(t, []string{"A", "B", "C"}, "A") - m.setState(SyncState) + m.setState(currentstate.SyncState) expectedNewBlockToSync := &types.Block{Header: &types.Header{Number: 1}} mockSyncer := newMockSyncer(nil, expectedNewBlockToSync, nil, false, nil) @@ -863,7 +885,7 @@ func TestRunSyncState_NewHeadReceivedFromPeer_CallsTxPoolResetWithHeaders(t *tes go func() { <-stateChangeDelay.C - m.setState(AcceptState) + m.setState(currentstate.AcceptState) }() m.runSyncState() @@ -876,7 +898,7 @@ func TestRunSyncState_NewHeadReceivedFromPeer_CallsTxPoolResetWithHeaders(t *tes func TestRunSyncState_BulkSyncWithPeer_CallsTxPoolResetWithHeaders(t *testing.T) { m := newMockIbft(t, []string{"A", "B", "C"}, "A") - m.setState(SyncState) + m.setState(currentstate.SyncState) expectedNewBlocksToSync := []*types.Block{ {Header: &types.Header{Number: 1}}, @@ -892,7 +914,7 @@ func TestRunSyncState_BulkSyncWithPeer_CallsTxPoolResetWithHeaders(t *testing.T) go func() { <-stateChangeDelay.C - m.setState(AcceptState) + m.setState(currentstate.AcceptState) }() m.runSyncState() @@ -914,10 +936,10 @@ func TestRunSyncState_Unlock_After_Sync(t *testing.T) { m := newMockIBFTWithMockBlockchain(t, pool, blockchain, "A") m.sealing = true - m.setState(SyncState) + m.setState(currentstate.SyncState) // Locking block #1 - m.state.locked = true + m.state.Lock() // Sync blocks to #3 expectedNewBlocksToSync := []*types.Block{ @@ -934,7 +956,7 @@ func TestRunSyncState_Unlock_After_Sync(t *testing.T) { go func() { <-stateChangeDelay.C - m.setState(AcceptState) + m.setState(currentstate.AcceptState) }() m.runSyncState() @@ -942,7 +964,7 @@ func TestRunSyncState_Unlock_After_Sync(t *testing.T) { // Validator should start new sequence from the next of the latest block and unlock block in state m.expect(expectResult{ sequence: 4, - state: AcceptState, + state: currentstate.AcceptState, locked: false, }) } @@ -1095,6 +1117,10 @@ func (t *mockTransition) Write(txn *types.Transaction) error { return nil } +func (t *mockTransition) GetNonce(addr types.Address) uint64 { + return 0 +} + type mockIbft struct { t *testing.T *Ibft @@ -1156,7 +1182,7 @@ func (m *mockIbft) addMessage(msg *proto.MessageReq) { from := m.pool.get(msg.From).Address() msg.From = from.String() - m.state.addMessage(msg) + m.state.AddMessage(msg) } func (m *mockIbft) Gossip(msg *proto.MessageReq) error { @@ -1194,8 +1220,13 @@ func newMockIbft(t *testing.T, accounts []string, validatorAccount string) *mock } ibft := &Ibft{ - logger: hclog.NewNullLogger(), - config: &consensus.Config{}, + logger: hclog.NewNullLogger(), + config: &consensus.Config{ + Params: &chain.Params{ + Forks: chain.AllForksEnabled, + ChainID: 100, + }, + }, blockchain: m, validatorKey: addr.priv, validatorKeyAddr: addr.Address(), @@ -1203,7 +1234,7 @@ func newMockIbft(t *testing.T, accounts []string, validatorAccount string) *mock isClosed: atomic.NewBool(false), updateCh: make(chan struct{}), operator: &operator{}, - state: newState(), + state: currentstate.NewState(), epochSize: DefaultEpochSize, metrics: consensus.NilMetrics(), exhaustingContracts: make(map[types.Address]struct{}), @@ -1212,7 +1243,7 @@ func newMockIbft(t *testing.T, accounts []string, validatorAccount string) *mock initIbftMechanism(PoA, ibft) // by default set the state to (1, 0) - ibft.state.view = proto.ViewMsg(1, 0) + ibft.state.SetView(proto.ViewMsg(1, 0)) m.Ibft = ibft @@ -1220,7 +1251,7 @@ func newMockIbft(t *testing.T, accounts []string, validatorAccount string) *mock assert.NoError(t, ibft.createKey()) // set the initial validators frrom the snapshot - ibft.state.validators = pool.ValidatorSet() + ibft.state.SetValidators(pool.ValidatorSet()) m.Ibft.transport = m @@ -1254,8 +1285,13 @@ func newMockIBFTWithMockBlockchain( } ibft := &Ibft{ - logger: hclog.NewNullLogger(), - config: &consensus.Config{}, + logger: hclog.NewNullLogger(), + config: &consensus.Config{ + Params: &chain.Params{ + Forks: chain.AllForksEnabled, + ChainID: 404, + }, + }, blockchain: m, validatorKey: addr.priv, validatorKeyAddr: addr.Address(), @@ -1263,7 +1299,7 @@ func newMockIBFTWithMockBlockchain( isClosed: atomic.NewBool(false), updateCh: make(chan struct{}), operator: &operator{}, - state: newState(), + state: currentstate.NewState(), epochSize: DefaultEpochSize, metrics: consensus.NilMetrics(), } @@ -1271,7 +1307,7 @@ func newMockIBFTWithMockBlockchain( initIbftMechanism(PoA, ibft) // by default set the state to (1, 0) - ibft.state.view = proto.ViewMsg(1, 0) + ibft.state.SetView(proto.ViewMsg(1, 0)) m.Ibft = ibft @@ -1279,7 +1315,7 @@ func newMockIBFTWithMockBlockchain( assert.NoError(t, ibft.createKey()) // set the initial validators frrom the snapshot - ibft.state.validators = pool.ValidatorSet() + ibft.state.SetValidators(pool.ValidatorSet()) m.Ibft.transport = m @@ -1287,7 +1323,7 @@ func newMockIBFTWithMockBlockchain( } type expectResult struct { - state IbftState + state currentstate.IbftState sequence uint64 round uint64 locked bool @@ -1302,11 +1338,11 @@ type expectResult struct { } func (m *mockIbft) expect(res expectResult) { - if sequence := m.state.view.Sequence; sequence != res.sequence { + if sequence := m.state.Sequence(); sequence != res.sequence { m.t.Fatalf("incorrect sequence got=%d expected=%d", sequence, res.sequence) } - if round := m.state.view.Round; round != res.round { + if round := m.state.Round(); round != res.round { m.t.Fatalf("incorrect round got=%d expected=%d", round, res.round) } @@ -1314,24 +1350,24 @@ func (m *mockIbft) expect(res expectResult) { m.t.Fatalf("incorrect state got=%s expected=%s", m.getState(), res.state) } - if size := len(m.state.prepared); uint64(size) != res.prepareMsgs { + if size := m.state.NumPrepared(); uint64(size) != res.prepareMsgs { m.t.Fatalf("incorrect prepared messages got=%d expected=%d", size, res.prepareMsgs) } - if size := len(m.state.committed); uint64(size) != res.commitMsgs { + if size := m.state.NumCommitted(); uint64(size) != res.commitMsgs { m.t.Fatalf("incorrect commit messages got=%d expected=%d", size, res.commitMsgs) } - if m.state.locked != res.locked { - m.t.Fatalf("incorrect locked got=%v expected=%v", m.state.locked, res.locked) + if m.state.IsLocked() != res.locked { + m.t.Fatalf("incorrect locked got=%v expected=%v", m.state.IsLocked(), res.locked) } if size := len(m.respMsg); uint64(size) != res.outgoing { m.t.Fatalf("incorrect outgoing messages got=%v expected=%v", size, res.outgoing) } - if !errors.Is(m.state.err, res.err) { - m.t.Fatalf("incorrect error got=%v expected=%v", m.state.err, res.err) + if !errors.Is(m.state.PeekError(), res.err) { + m.t.Fatalf("incorrect error got=%v expected=%v", m.state.PeekError(), res.err) } } @@ -1667,6 +1703,95 @@ func TestGetIBFTForks(t *testing.T) { } } +func TestState_FaultyNodes(t *testing.T) { + t.Parallel() + + cases := []struct { + Network, Faulty uint64 + }{ + {1, 0}, + {2, 0}, + {3, 0}, + {4, 1}, + {5, 1}, + {6, 1}, + {7, 2}, + {8, 2}, + {9, 2}, + } + for _, c := range cases { + pool := newTesterAccountPool(int(c.Network)) + vals := pool.ValidatorSet() + assert.Equal(t, vals.MaxFaultyNodes(), int(c.Faulty)) + } +} + +func TestState_AddMessages(t *testing.T) { + t.Parallel() + + pool := newTesterAccountPool() + pool.add("A", "B", "C", "D") + + c := currentstate.NewState() + c.SetValidators(pool.ValidatorSet()) + + msg := func(acct string, typ proto.MessageReq_Type, round ...uint64) *proto.MessageReq { + msg := &proto.MessageReq{ + From: pool.get(acct).Address().String(), + Type: typ, + View: &proto.View{Round: 0}, + } + r := uint64(0) + + if len(round) > 0 { + r = round[0] + } + + msg.View.Round = r + + return msg + } + + // -- test committed messages -- + c.AddMessage(msg("A", proto.MessageReq_Commit)) + c.AddMessage(msg("B", proto.MessageReq_Commit)) + c.AddMessage(msg("B", proto.MessageReq_Commit)) + + assert.Equal(t, c.NumCommitted(), 2) + + // -- test prepare messages -- + c.AddMessage(msg("C", proto.MessageReq_Prepare)) + c.AddMessage(msg("C", proto.MessageReq_Prepare)) + c.AddMessage(msg("D", proto.MessageReq_Prepare)) + + assert.Equal(t, c.NumPrepared(), 2) +} + +func Test_increaseHeaderGasIfNeeded(t *testing.T) { + var ( + header = &types.Header{GasLimit: 100_000} + transition = &state.Transition{} + tx1 = &types.Transaction{Gas: 63_000} + tx2 = &types.Transaction{Gas: 29_000} + ) + + // before tx1 + transition.HookTotalGas(func() uint64 { + return 92_000 // gas left 8000, not enought for tx1 + }) + + increaseHeaderGasIfNeeded(transition, header, tx1) + assert.Equal(t, uint64(155000), header.GasLimit) + + // after tx1 + transition.HookTotalGas(func() uint64 { + return 92_000 + 28_000 // gas left 35000, enough for tx2 + }) + + increaseHeaderGasIfNeeded(transition, header, tx2) + assert.Equal(t, uint64(155000), header.GasLimit) +} + func Test_shouldBanishTx(t *testing.T) { mockTx := &types.Transaction{ Nonce: 0, diff --git a/consensus/ibft/msg_queue.go b/consensus/ibft/msg_queue.go index fd7c8bc087..dc7a7e1716 100644 --- a/consensus/ibft/msg_queue.go +++ b/consensus/ibft/msg_queue.go @@ -4,6 +4,7 @@ import ( "container/heap" "sync" + "github.com/dogechain-lab/dogechain/consensus/ibft/currentstate" "github.com/dogechain-lab/dogechain/consensus/ibft/proto" ) @@ -32,7 +33,7 @@ func (m *msgQueue) pushMessage(task *msgTask) { } // readMessage reads the message from a message queue, based on the current state and view -func (m *msgQueue) readMessage(state IbftState, current *proto.View) *msgTask { +func (m *msgQueue) readMessage(state currentstate.IbftState, current *proto.View) *msgTask { m.queueLock.Lock() defer m.queueLock.Unlock() @@ -46,7 +47,7 @@ func (m *msgQueue) readMessage(state IbftState, current *proto.View) *msgTask { msg := queue.head() // check if the message is from the future - if state == RoundChangeState { + if state == currentstate.RoundChangeState { // if we are in RoundChangeState we only care about sequence // since we are interested in knowing all the possible rounds if msg.view.Sequence > current.Sequence { @@ -76,11 +77,11 @@ func (m *msgQueue) readMessage(state IbftState, current *proto.View) *msgTask { } // getQueue checks the passed in state, and returns the corresponding message queue -func (m *msgQueue) getQueue(state IbftState) *msgQueueImpl { - if state == RoundChangeState { +func (m *msgQueue) getQueue(state currentstate.IbftState) *msgQueueImpl { + if state == currentstate.RoundChangeState { // round change return &m.roundChangeStateQueue - } else if state == AcceptState { + } else if state == currentstate.AcceptState { // preprepare return &m.acceptStateQueue } else { @@ -112,16 +113,16 @@ func protoTypeToMsg(msgType proto.MessageReq_Type) MsgType { } // msgToState converts the message type to an IbftState -func msgToState(msg MsgType) IbftState { +func msgToState(msg MsgType) currentstate.IbftState { if msg == msgRoundChange { // round change - return RoundChangeState + return currentstate.RoundChangeState } else if msg == msgPreprepare { // preprepare - return AcceptState + return currentstate.AcceptState } else if msg == msgPrepare || msg == msgCommit { // prepare and commit - return ValidateState + return currentstate.ValidateState } panic("BUG: not expected") diff --git a/consensus/ibft/msg_queue_test.go b/consensus/ibft/msg_queue_test.go index 685fd36d0d..4fe8b84956 100644 --- a/consensus/ibft/msg_queue_test.go +++ b/consensus/ibft/msg_queue_test.go @@ -3,6 +3,7 @@ package ibft import ( "testing" + "github.com/dogechain-lab/dogechain/consensus/ibft/currentstate" "github.com/dogechain-lab/dogechain/consensus/ibft/proto" "github.com/stretchr/testify/assert" ) @@ -27,7 +28,7 @@ func TestMsgQueue_RoundChangeState(t *testing.T) { m.pushMessage(mockQueueMsg("B", msgCommit, proto.ViewMsg(1, 0))) // we only read round state messages - assert.Nil(t, m.readMessage(RoundChangeState, proto.ViewMsg(1, 0))) + assert.Nil(t, m.readMessage(currentstate.RoundChangeState, proto.ViewMsg(1, 0))) } // insert old round change messages @@ -35,7 +36,7 @@ func TestMsgQueue_RoundChangeState(t *testing.T) { m.pushMessage(mockQueueMsg("C", msgRoundChange, proto.ViewMsg(1, 1))) // the round change message is old - assert.Nil(t, m.readMessage(RoundChangeState, proto.ViewMsg(2, 0))) + assert.Nil(t, m.readMessage(currentstate.RoundChangeState, proto.ViewMsg(2, 0))) assert.Zero(t, m.roundChangeStateQueue.Len()) } @@ -45,11 +46,11 @@ func TestMsgQueue_RoundChangeState(t *testing.T) { m.pushMessage(mockQueueMsg("E", msgRoundChange, proto.ViewMsg(1, 1))) m.pushMessage(mockQueueMsg("F", msgRoundChange, proto.ViewMsg(2, 1))) - msg1 := m.readMessage(RoundChangeState, proto.ViewMsg(2, 0)) + msg1 := m.readMessage(currentstate.RoundChangeState, proto.ViewMsg(2, 0)) assert.NotNil(t, msg1) assert.Equal(t, msg1.obj.From, "F") - msg2 := m.readMessage(RoundChangeState, proto.ViewMsg(2, 0)) + msg2 := m.readMessage(currentstate.RoundChangeState, proto.ViewMsg(2, 0)) assert.NotNil(t, msg2) assert.Equal(t, msg2.obj.From, "D") } diff --git a/consensus/ibft/pos.go b/consensus/ibft/pos.go index c6a6f50c2c..a36251351e 100644 --- a/consensus/ibft/pos.go +++ b/consensus/ibft/pos.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/dogechain-lab/dogechain/consensus/ibft/validator" "github.com/dogechain-lab/dogechain/contracts/systemcontracts" "github.com/dogechain-lab/dogechain/contracts/validatorset" validatorsetHelper "github.com/dogechain-lab/dogechain/helper/validatorset" @@ -127,7 +128,15 @@ func (pos *PoSMechanism) verifyBlockHook(blockParam interface{}) error { } if pos.ibft.IsLastOfEpoch(block.Number()) && len(block.Transactions) > 0 { - return errBlockVerificationFailed + number := block.Number() + coinbase := block.Header.Miner // it must be a the real miner + + // it could only include system transactions + for _, tx := range block.Transactions { + if !pos.ibft.IsSystemTransaction(number, coinbase, tx) { + return errBlockVerificationFailed + } + } } return nil @@ -192,13 +201,13 @@ func (pos *PoSMechanism) ShouldWriteTransactions(blockNumber uint64) bool { // getNextValidators is a helper function for fetching the validator set // from the ValidatorSet SC -func (pos *PoSMechanism) getNextValidators(header *types.Header) (ValidatorSet, error) { +func (pos *PoSMechanism) getNextValidators(header *types.Header) (validator.Validators, error) { transition, err := pos.ibft.executor.BeginTxn(header.StateRoot, header, types.ZeroAddress) if err != nil { return nil, err } - return validatorset.QueryValidators(transition, pos.ibft.validatorKeyAddr) + return validatorset.QueryValidators(transition, pos.ibft.validatorKeyAddr, header.GasLimit) } // updateSnapshotValidators updates validators in snapshot at given height diff --git a/consensus/ibft/snapshot.go b/consensus/ibft/snapshot.go index d3fa39c65a..c7cffa6db9 100644 --- a/consensus/ibft/snapshot.go +++ b/consensus/ibft/snapshot.go @@ -11,6 +11,7 @@ import ( "sync/atomic" "github.com/dogechain-lab/dogechain/consensus/ibft/proto" + "github.com/dogechain-lab/dogechain/consensus/ibft/validator" "github.com/dogechain-lab/dogechain/types" "github.com/hashicorp/go-hclog" ) @@ -254,7 +255,7 @@ type Snapshot struct { Votes []*Vote // current set of validators - Set ValidatorSet + Set validator.Validators } // snapshotMetadata defines the metadata for the snapshot @@ -307,7 +308,7 @@ func (s *Snapshot) Copy() *Snapshot { // Do not need to copy Number and Hash ss := &Snapshot{ Votes: make([]*Vote, len(s.Votes)), - Set: ValidatorSet{}, + Set: validator.Validators{}, } for indx, vote := range s.Votes { @@ -402,7 +403,7 @@ func (s *snapshotStore) loadFromPath(path string, l hclog.Logger) error { // saveToPath saves the snapshot store as a file to the specified path func (s *snapshotStore) saveToPath(path string) error { // Write snapshots - if err := writeDataStore(filepath.Join(path, "snapshots"), s.list); err != nil { + if err := writeDataStore(path, "snapshots", s.list); err != nil { return err } @@ -410,7 +411,7 @@ func (s *snapshotStore) saveToPath(path string) error { meta := &snapshotMetadata{ LastBlock: s.lastNumber, } - if err := writeDataStore(filepath.Join(path, "metadata"), meta); err != nil { + if err := writeDataStore(path, "metadata", meta); err != nil { return err } @@ -531,14 +532,39 @@ func readDataStore(path string, obj interface{}) error { } // writeDataStore attempts to write the specific file to file storage -func writeDataStore(path string, obj interface{}) error { +func writeDataStore(dir, filename string, obj interface{}) error { data, err := json.Marshal(obj) if err != nil { return err } - //nolint:gosec - if err := ioutil.WriteFile(path, data, 0755); err != nil { + // atomically overwrite file + // https://stackoverflow.com/questions/30385225/is-there-an-os-independent-way-to-atomically-overwrite-a-file + // create a temporary file + tmpFile, err := os.CreateTemp(dir, filename+"*.tmp") + if err != nil { + return err + } + + // write the data to the temporary file + if _, err := tmpFile.Write(data); err != nil { + return err + } + + // close the temporary file + if err := tmpFile.Close(); err != nil { + return err + } + + path := filepath.Join(dir, filename) + + // mv the temporary file to the final file + if err := os.Rename(tmpFile.Name(), path); err != nil { + return err + } + + // chmod file to 0644 + if err := os.Chmod(path, 0644); err != nil { return err } diff --git a/consensus/ibft/snapshot_test.go b/consensus/ibft/snapshot_test.go index e41d4ce2c0..c66f3894a8 100644 --- a/consensus/ibft/snapshot_test.go +++ b/consensus/ibft/snapshot_test.go @@ -10,6 +10,7 @@ import ( "github.com/dogechain-lab/dogechain/blockchain" "github.com/dogechain-lab/dogechain/chain" "github.com/dogechain-lab/dogechain/consensus" + "github.com/dogechain-lab/dogechain/consensus/ibft/validator" "github.com/dogechain-lab/dogechain/crypto" "github.com/dogechain-lab/dogechain/helper/common" "github.com/dogechain-lab/dogechain/types" @@ -122,8 +123,8 @@ func (ap *testerAccountPool) get(name string) *testerAccount { return nil } -func (ap *testerAccountPool) ValidatorSet() ValidatorSet { - v := ValidatorSet{} +func (ap *testerAccountPool) ValidatorSet() validator.Validators { + v := validator.Validators{} for _, i := range ap.accounts { v = append(v, i.Address()) } @@ -250,7 +251,7 @@ func TestSnapshot_setupSnapshot(t *testing.T) { pool.add(candidateValidators...) - newSnapshot := func(n uint64, set ValidatorSet, votes []*Vote) *Snapshot { + newSnapshot := func(n uint64, set validator.Validators, votes []*Vote) *Snapshot { return &Snapshot{ Number: n, Set: set, @@ -734,7 +735,7 @@ func TestSnapshot_ProcessHeaders(t *testing.T) { if result != nil { resSnap := &Snapshot{ Votes: []*Vote{}, - Set: ValidatorSet{}, + Set: validator.Validators{}, } // check validators for _, i := range result.validators { diff --git a/consensus/ibft/state.go b/consensus/ibft/state.go deleted file mode 100644 index bd89ba9ba5..0000000000 --- a/consensus/ibft/state.go +++ /dev/null @@ -1,308 +0,0 @@ -package ibft - -import ( - "fmt" - "sync/atomic" - - "github.com/dogechain-lab/dogechain/consensus/ibft/proto" - "github.com/dogechain-lab/dogechain/types" -) - -type IbftState uint32 - -// Define the states in IBFT -const ( - AcceptState IbftState = iota - RoundChangeState - ValidateState - CommitState - SyncState -) - -// String returns the string representation of the passed in state -func (i IbftState) String() string { - switch i { - case AcceptState: - return "AcceptState" - - case RoundChangeState: - return "RoundChangeState" - - case ValidateState: - return "ValidateState" - - case CommitState: - return "CommitState" - - case SyncState: - return "SyncState" - } - - panic(fmt.Sprintf("BUG: Ibft state not found %d", i)) -} - -// currentState defines the current state object in IBFT -type currentState struct { - // validators represent the current validator set - validators ValidatorSet - - // state is the current state - state uint64 - - // The proposed block - block *types.Block - - // The selected proposer - proposer types.Address - - // Current view - view *proto.View - - // List of prepared messages - prepared map[types.Address]*proto.MessageReq - - // List of committed messages - committed map[types.Address]*proto.MessageReq - - // List of round change messages - roundMessages map[uint64]map[types.Address]*proto.MessageReq - - // Locked signals whether the proposal is locked - locked bool - - // Describes whether there has been an error during the computation - err error -} - -// newState creates a new state with reset round messages -func newState() *currentState { - c := ¤tState{} - c.resetRoundMsgs() - - return c -} - -// getState returns the current state -func (c *currentState) getState() IbftState { - stateAddr := &c.state - - return IbftState(atomic.LoadUint64(stateAddr)) -} - -// setState sets the current state -func (c *currentState) setState(s IbftState) { - stateAddr := &c.state - - atomic.StoreUint64(stateAddr, uint64(s)) -} - -// NumValid returns the number of required messages -func (c *currentState) NumValid() int { - // According to the IBFT spec, the number of valid messages - // needs to be 2F + 1 - // The 1 missing from this equation is accounted for elsewhere - // (the current node tallying the messages will include its own message) - return 2 * c.validators.MaxFaultyNodes() -} - -// getErr returns the current error, if any, and consumes it -func (c *currentState) getErr() error { - err := c.err - c.err = nil - - return err -} - -func (c *currentState) maxRound() (maxRound uint64, found bool) { - num := c.validators.MaxFaultyNodes() + 1 - - for k, round := range c.roundMessages { - if len(round) < num { - continue - } - - if maxRound < k { - maxRound = k - found = true - } - } - - return -} - -// resetRoundMsgs resets the prepared, committed and round messages in the current state -func (c *currentState) resetRoundMsgs() { - c.prepared = map[types.Address]*proto.MessageReq{} - c.committed = map[types.Address]*proto.MessageReq{} - c.roundMessages = map[uint64]map[types.Address]*proto.MessageReq{} -} - -// CalcProposer calculates the proposer and sets it to the state -func (c *currentState) CalcProposer(lastProposer types.Address) { - c.proposer = c.validators.CalcProposer(c.view.Round, lastProposer) -} - -func (c *currentState) lock() { - c.locked = true -} - -func (c *currentState) unlock() { - c.block = nil - c.locked = false -} - -// cleanRound deletes the specific round messages -func (c *currentState) cleanRound(round uint64) { - delete(c.roundMessages, round) -} - -// AddRoundMessage adds a message to the round, and returns the round message size -func (c *currentState) AddRoundMessage(msg *proto.MessageReq) int { - if msg.Type != proto.MessageReq_RoundChange { - return 0 - } - - c.addMessage(msg) - - return len(c.roundMessages[msg.View.Round]) -} - -// addPrepared adds a prepared message -func (c *currentState) addPrepared(msg *proto.MessageReq) { - if msg.Type != proto.MessageReq_Prepare { - return - } - - c.addMessage(msg) -} - -// addCommitted adds a committed message -func (c *currentState) addCommitted(msg *proto.MessageReq) { - if msg.Type != proto.MessageReq_Commit { - return - } - - c.addMessage(msg) -} - -// addMessage adds a new message to one of the following message lists: committed, prepared, roundMessages -func (c *currentState) addMessage(msg *proto.MessageReq) { - addr := msg.FromAddr() - if !c.validators.Includes(addr) { - // only include messages from validators - return - } - - switch { - case msg.Type == proto.MessageReq_Commit: - c.committed[addr] = msg - case msg.Type == proto.MessageReq_Prepare: - c.prepared[addr] = msg - case msg.Type == proto.MessageReq_RoundChange: - view := msg.View - if _, ok := c.roundMessages[view.Round]; !ok { - c.roundMessages[view.Round] = map[types.Address]*proto.MessageReq{} - } - - c.roundMessages[view.Round][addr] = msg - } -} - -// numPrepared returns the number of messages in the prepared message list -func (c *currentState) numPrepared() int { - return len(c.prepared) -} - -// numCommitted returns the number of messages in the committed message list -func (c *currentState) numCommitted() int { - return len(c.committed) -} - -type ValidatorSet []types.Address - -// CalcProposer calculates the address of the next proposer, from the validator set -func (v *ValidatorSet) CalcProposer(round uint64, lastProposer types.Address) types.Address { - var seed uint64 - - if lastProposer == types.ZeroAddress { - seed = round - } else { - offset := 0 - if indx := v.Index(lastProposer); indx != -1 { - offset = indx - } - - seed = uint64(offset) + round + 1 - } - - pick := seed % uint64(v.Len()) - - return (*v)[pick] -} - -// Add adds a new address to the validator set -func (v *ValidatorSet) Add(addr types.Address) { - *v = append(*v, addr) -} - -// Del removes an address from the validator set -func (v *ValidatorSet) Del(addr types.Address) { - for indx, i := range *v { - if i == addr { - *v = append((*v)[:indx], (*v)[indx+1:]...) - } - } -} - -// Len returns the size of the validator set -func (v *ValidatorSet) Len() int { - return len(*v) -} - -// Equal checks if 2 validator sets are equal -func (v *ValidatorSet) Equal(vv *ValidatorSet) bool { - if len(*v) != len(*vv) { - return false - } - - for indx := range *v { - if (*v)[indx] != (*vv)[indx] { - return false - } - } - - return true -} - -// Index returns the index of the passed in address in the validator set. -// Returns -1 if not found -func (v *ValidatorSet) Index(addr types.Address) int { - for indx, i := range *v { - if i == addr { - return indx - } - } - - return -1 -} - -// Includes checks if the address is in the validator set -func (v *ValidatorSet) Includes(addr types.Address) bool { - return v.Index(addr) != -1 -} - -// MaxFaultyNodes returns the maximum number of allowed faulty nodes (F), based on the current validator set -func (v *ValidatorSet) MaxFaultyNodes() int { - // N -> number of nodes in IBFT - // F -> number of faulty nodes - // - // N = 3F + 1 - // => F = (N - 1) / 3 - // - // IBFT tolerates 1 failure with 4 nodes - // 4 = 3 * 1 + 1 - // To tolerate 2 failures, IBFT requires 7 nodes - // 7 = 3 * 2 + 1 - // It should always take the floor of the result - return (len(*v) - 1) / 3 -} diff --git a/consensus/ibft/state_test.go b/consensus/ibft/state_test.go deleted file mode 100644 index 8801ae7ff0..0000000000 --- a/consensus/ibft/state_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package ibft - -import ( - "testing" - - "github.com/dogechain-lab/dogechain/consensus/ibft/proto" - "github.com/stretchr/testify/assert" -) - -func TestState_FaultyNodes(t *testing.T) { - cases := []struct { - Network, Faulty uint64 - }{ - {1, 0}, - {2, 0}, - {3, 0}, - {4, 1}, - {5, 1}, - {6, 1}, - {7, 2}, - {8, 2}, - {9, 2}, - } - for _, c := range cases { - pool := newTesterAccountPool(int(c.Network)) - vals := pool.ValidatorSet() - assert.Equal(t, vals.MaxFaultyNodes(), int(c.Faulty)) - } -} - -func TestState_AddMessages(t *testing.T) { - pool := newTesterAccountPool() - pool.add("A", "B", "C", "D") - - c := newState() - c.validators = pool.ValidatorSet() - - msg := func(acct string, typ proto.MessageReq_Type, round ...uint64) *proto.MessageReq { - msg := &proto.MessageReq{ - From: pool.get(acct).Address().String(), - Type: typ, - View: &proto.View{Round: 0}, - } - r := uint64(0) - - if len(round) > 0 { - r = round[0] - } - - msg.View.Round = r - - return msg - } - - // -- test committed messages -- - c.addMessage(msg("A", proto.MessageReq_Commit)) - c.addMessage(msg("B", proto.MessageReq_Commit)) - c.addMessage(msg("B", proto.MessageReq_Commit)) - - assert.Equal(t, c.numCommitted(), 2) - - // -- test prepare messages -- - c.addMessage(msg("C", proto.MessageReq_Prepare)) - c.addMessage(msg("C", proto.MessageReq_Prepare)) - c.addMessage(msg("D", proto.MessageReq_Prepare)) - - assert.Equal(t, c.numPrepared(), 2) -} diff --git a/consensus/ibft/timeout.go b/consensus/ibft/timeout.go deleted file mode 100644 index dcc6bcf2a1..0000000000 --- a/consensus/ibft/timeout.go +++ /dev/null @@ -1,28 +0,0 @@ -package ibft - -import ( - "math" - "time" -) - -const ( - baseTimeout = 10 * time.Second - maxTimeout = 300 * time.Second -) - -// exponentialTimeout calculates the timeout duration in seconds as exponential function -// where maximum value returned can't exceed 300 seconds -// t = 10 + 2^exponent where exponent > 0 -// t = 10 where exponent = 0 -func exponentialTimeout(exponent uint64) time.Duration { - if exponent > 8 { - return maxTimeout - } - - timeout := baseTimeout - if exponent > 0 { - timeout += time.Duration(math.Pow(2, float64(exponent))) * time.Second - } - - return timeout -} diff --git a/consensus/ibft/timeout_test.go b/consensus/ibft/timeout_test.go deleted file mode 100644 index 563f7ecc7a..0000000000 --- a/consensus/ibft/timeout_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package ibft - -import ( - "github.com/stretchr/testify/assert" - "testing" - "time" -) - -func TestExponentialTimeout(t *testing.T) { - testCases := []struct { - description string - exponent uint64 - expected time.Duration - }{ - {"for exponent 0 returns 10s", 0, 10 * time.Second}, - {"for exponent 1 returns 12s", 1, (10 + 2) * time.Second}, - {"for exponent 2 returns 14s", 2, (10 + 4) * time.Second}, - {"for exponent 8 returns 256s", 8, (10 + 256) * time.Second}, - {"for exponent 9 returns 300s", 9, 300 * time.Second}, - {"for exponent 10 returns 300s", 10, 5 * time.Minute}, - } - - for _, test := range testCases { - t.Run(test.description, func(t *testing.T) { - timeout := exponentialTimeout(test.exponent) - - assert.Equal(t, test.expected, timeout) - }) - } -} diff --git a/consensus/ibft/validator/validators.go b/consensus/ibft/validator/validators.go new file mode 100644 index 0000000000..af23bca3e1 --- /dev/null +++ b/consensus/ibft/validator/validators.go @@ -0,0 +1,92 @@ +package validator + +import "github.com/dogechain-lab/dogechain/types" + +type Validators []types.Address + +// CalcProposer calculates the address of the next proposer, from the validator set +func (v *Validators) CalcProposer(round uint64, lastProposer types.Address) types.Address { + var seed uint64 + + if lastProposer == types.ZeroAddress { + seed = round + } else { + offset := 0 + if indx := v.Index(lastProposer); indx != -1 { + offset = indx + } + + seed = uint64(offset) + round + 1 + } + + pick := seed % uint64(v.Len()) + + return (*v)[pick] +} + +// Add adds a new address to the validator set +func (v *Validators) Add(addr types.Address) { + *v = append(*v, addr) +} + +// Del removes an address from the validator set +func (v *Validators) Del(addr types.Address) { + for indx, i := range *v { + if i == addr { + *v = append((*v)[:indx], (*v)[indx+1:]...) + } + } +} + +// Len returns the size of the validator set +func (v *Validators) Len() int { + return len(*v) +} + +// Equal checks if 2 validator sets are equal +func (v *Validators) Equal(vv *Validators) bool { + if len(*v) != len(*vv) { + return false + } + + for indx := range *v { + if (*v)[indx] != (*vv)[indx] { + return false + } + } + + return true +} + +// Index returns the index of the passed in address in the validator set. +// Returns -1 if not found +func (v *Validators) Index(addr types.Address) int { + for indx, i := range *v { + if i == addr { + return indx + } + } + + return -1 +} + +// Includes checks if the address is in the validator set +func (v *Validators) Includes(addr types.Address) bool { + return v.Index(addr) != -1 +} + +// MaxFaultyNodes returns the maximum number of allowed faulty nodes (F), based on the current validator set +func (v *Validators) MaxFaultyNodes() int { + // N -> number of nodes in IBFT + // F -> number of faulty nodes + // + // N = 3F + 1 + // => F = (N - 1) / 3 + // + // IBFT tolerates 1 failure with 4 nodes + // 4 = 3 * 1 + 1 + // To tolerate 2 failures, IBFT requires 7 nodes + // 7 = 3 * 2 + 1 + // It should always take the floor of the result + return (len(*v) - 1) / 3 +} diff --git a/contracts/abis/encode.go b/contracts/abis/encode.go new file mode 100644 index 0000000000..6f8fa10790 --- /dev/null +++ b/contracts/abis/encode.go @@ -0,0 +1,73 @@ +package abis + +import ( + "bytes" + "errors" + + "github.com/umbracle/go-web3/abi" +) + +var ( + ErrNoMethod = errors.New("no method in abi") + ErrInvalidSignature = errors.New("invalid signature") + ErrResultTypeCasting = errors.New("failed to casting type to map") +) + +func EncodeTxMethod(method *abi.Method, abiArgs map[string]interface{}) (input []byte, err error) { + if method == nil { + return nil, ErrNoMethod + } + + input = append(input, method.ID()...) + + // no input + if abiArgs == nil { + return + } + + // rlp marshal args + args, err := method.Inputs.Encode(abiArgs) + if err != nil { + return nil, err + } + + return append(input, args...), nil +} + +func decodeABITypeVal(typ *abi.Type, val []byte) (map[string]interface{}, error) { + result, err := typ.Decode(val) + if err != nil { + return nil, err + } + + output, ok := result.(map[string]interface{}) + if !ok { + return nil, ErrResultTypeCasting + } + + return output, nil +} + +func DecodeTxMethodInput(method *abi.Method, val []byte) (map[string]interface{}, error) { + if method == nil { + return nil, ErrNoMethod + } + + if len(val) < 4 { + return nil, ErrInvalidSignature + } + + if !bytes.EqualFold(method.ID(), val[:4]) { + return nil, ErrInvalidSignature + } + + return decodeABITypeVal(method.Inputs, val[4:]) +} + +func DecodeTxMethodOutput(method *abi.Method, val []byte) (map[string]interface{}, error) { + if method == nil { + return nil, ErrNoMethod + } + + return decodeABITypeVal(method.Outputs, val) +} diff --git a/contracts/abis/json.go b/contracts/abis/json.go index 2ac7ed10fd..842d6b3db4 100644 --- a/contracts/abis/json.go +++ b/contracts/abis/json.go @@ -7,6 +7,26 @@ const ValidatorSetJSONABI = `[ "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "ActiveValidatorsLengthChanged", + "type": "event" + }, { "anonymous": false, "inputs": @@ -20,17 +40,183 @@ const ValidatorSetJSONABI = `[ { "indexed": true, "internalType": "address", - "name": "oldToken", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reward", + "type": "uint256" + } + ], + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "address", + "name": "sender", "type": "address" }, { "indexed": true, "internalType": "address", - "name": "newToken", + "name": "validator", "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Delegated", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "EpochBlockIntervalChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "FelonyThresholdChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "MinDelegatorStakeAmountChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "MinStakePeriodChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "MinValidatorChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "MinValidatorStakeAmountChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" } ], - "name": "GovernorTokenSet", + "name": "MisdemeanorThresholdChanged", "type": "event" }, { @@ -40,23 +226,37 @@ const ValidatorSetJSONABI = `[ { "indexed": true, "internalType": "address", - "name": "sender", + "name": "prevValue", "type": "address" }, + { + "indexed": true, + "internalType": "address", + "name": "newValue", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ { "indexed": true, "internalType": "uint256", - "name": "oldMinimum", + "name": "prevValue", "type": "uint256" }, { "indexed": true, "internalType": "uint256", - "name": "newMinimum", + "name": "newValue", "type": "uint256" } ], - "name": "MinimumSet", + "name": "RewardPerBlockChanged", "type": "event" }, { @@ -66,17 +266,17 @@ const ValidatorSetJSONABI = `[ { "indexed": true, "internalType": "address", - "name": "previousOwner", + "name": "prevValue", "type": "address" }, { "indexed": true, "internalType": "address", - "name": "newOwner", + "name": "newValue", "type": "address" } ], - "name": "OwnershipTransferred", + "name": "RewardTokenChanged", "type": "event" }, { @@ -86,17 +286,17 @@ const ValidatorSetJSONABI = `[ { "indexed": true, "internalType": "address", - "name": "sender", + "name": "prevValue", "type": "address" }, { "indexed": true, - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "address", + "name": "newValue", + "type": "address" } ], - "name": "Staked", + "name": "StakeTokenChanged", "type": "event" }, { @@ -111,18 +311,24 @@ const ValidatorSetJSONABI = `[ }, { "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, "internalType": "uint256", - "name": "oldThreshold", + "name": "amount", "type": "uint256" }, { - "indexed": true, + "indexed": false, "internalType": "uint256", - "name": "newThreshold", + "name": "reward", "type": "uint256" } ], - "name": "ThresholdSet", + "name": "Undelegated", "type": "event" }, { @@ -132,17 +338,31 @@ const ValidatorSetJSONABI = `[ { "indexed": true, "internalType": "address", - "name": "sender", + "name": "validator", "type": "address" - }, + } + ], + "name": "ValidatorAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ { "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], - "name": "Unstaked", + "name": "ValidatorDeposited", "type": "event" }, { @@ -151,18 +371,38 @@ const ValidatorSetJSONABI = `[ [ { "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" + "internalType": "uint256", + "name": "prevValue", + "type": "uint256" }, + { + "indexed": true, + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "ValidatorJailEpochLengthChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ { "indexed": true, "internalType": "address", - "name": "account", + "name": "validator", "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" } ], - "name": "ValidatorAdded", + "name": "ValidatorJailed", "type": "event" }, { @@ -172,71 +412,990 @@ const ValidatorSetJSONABI = `[ { "indexed": true, "internalType": "address", - "name": "sender", + "name": "validator", "type": "address" }, + { + "indexed": false, + "internalType": "uint256", + "name": "status", + "type": "uint256" + } + ], + "name": "ValidatorModified", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ { "indexed": true, "internalType": "address", - "name": "account", + "name": "validator", "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" } ], - "name": "ValidatorDeleted", + "name": "ValidatorNameChanged", "type": "event" }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "ValidatorReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "ValidatorRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": + [ + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashes", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "ValidatorSlashed", + "type": "event" + }, + { + "inputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "_validators", + "outputs": + [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + } + ], + "name": "activateValidator", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + } + ], + "name": "claim", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [], + "name": "currentEpoch", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "delegate", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "delegatorStakeAmount", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "delegators", + "outputs": + [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardDebt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "changedAt", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "deposit", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [], + "name": "detroitMigration", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + } + ], + "name": "disableValidator", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "exists", + "outputs": + [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + } + ], + "name": "forceUnJailValidator", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [], + "name": "getActiveValidatorsLength", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getDelegatorStakedAmount", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getDelegatorStakedReward", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getDelegatorTotalStakedAmount", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getDelegatorsLength", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getEpochBlockInterval", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getFelonyThreshold", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getMinDelegatorStakeAmount", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getMinStakePeriod", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getMinValidatorLength", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getMinValidatorStakeAmount", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getMisdemeanorThreshold", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getRewardPerBlock", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getStakeToken", + "outputs": + [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "getValidatorDelegatorLength", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "getValidatorDelegators", + "outputs": + [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getValidatorJailEpochLength", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getValidatorLength", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "getValidatorStakedAmount", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "getValidatorStakedReward", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "getValidatorStatus", + "outputs": + [ + { + "internalType": "enum ValidatorSet.Status", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "getValidators", + "outputs": + [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "isValidator", + "outputs": + [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + } + ], + "name": "jailValidator", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [], + "name": "nextEpoch", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "owner", + "outputs": + [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [], + "name": "paused", + "outputs": + [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "registerValidator", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + } + ], + "name": "releaseValidatorFromJail", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [], + "name": "releasedReward", + "outputs": + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + } + ], + "name": "removeFromValidator", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + } + ], + "name": "removeValidator", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [], + "name": "rewardToken", + "outputs": + [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "setActiveValidatorsLength", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "setEpochBlockInterval", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": + [ + { + "internalType": "uint256", + "name": "newValue", + "type": "uint256" + } + ], + "name": "setFelonyThreshold", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { "internalType": "uint256", - "name": "", + "name": "newValue", "type": "uint256" } ], - "name": "_validators", + "name": "setMinDelegatorStakeAmount", "outputs": - [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", + [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { - "internalType": "address", - "name": "account", - "type": "address" + "internalType": "uint32", + "name": "newValue", + "type": "uint32" } ], - "name": "accountStake", + "name": "setMinStakePeriod", "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "uint256", - "name": "", + "name": "newValue", "type": "uint256" } ], - "stateMutability": "view", + "name": "setMinValidatorLength", + "outputs": + [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { - "internalType": "address", - "name": "account", - "type": "address" + "internalType": "uint256", + "name": "newValue", + "type": "uint256" } ], - "name": "addValidator", + "name": "setMinValidatorStakeAmount", "outputs": [], "stateMutability": "nonpayable", @@ -246,12 +1405,12 @@ const ValidatorSetJSONABI = `[ "inputs": [ { - "internalType": "address", - "name": "account", - "type": "address" + "internalType": "uint256", + "name": "newValue", + "type": "uint256" } ], - "name": "deleteValidator", + "name": "setMisdemeanorThreshold", "outputs": [], "stateMutability": "nonpayable", @@ -260,16 +1419,25 @@ const ValidatorSetJSONABI = `[ { "inputs": [], - "name": "governor", + "name": "setPause", "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { - "internalType": "address", - "name": "", - "type": "address" + "internalType": "uint256", + "name": "newValue", + "type": "uint256" } ], - "stateMutability": "view", + "name": "setRewardPerBlock", + "outputs": + [], + "stateMutability": "nonpayable", "type": "function" }, { @@ -277,56 +1445,61 @@ const ValidatorSetJSONABI = `[ [ { "internalType": "address", - "name": "account", + "name": "newValue", "type": "address" } ], - "name": "isValidator", + "name": "setRewardToken", "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { - "internalType": "bool", - "name": "", - "type": "bool" + "internalType": "address", + "name": "newValue", + "type": "address" } ], - "stateMutability": "view", + "name": "setStakeToken", + "outputs": + [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": - [], - "name": "minimum", - "outputs": [ { "internalType": "uint256", - "name": "", + "name": "newValue", "type": "uint256" } ], - "stateMutability": "view", + "name": "setValidatorJailEpochLength", + "outputs": + [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": - [], - "name": "owner", - "outputs": [ { "internalType": "address", - "name": "", + "name": "validatorAddress", "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" } ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": - [], - "name": "renounceOwnership", + "name": "setValidatorName", "outputs": [], "stateMutability": "nonpayable", @@ -337,11 +1510,11 @@ const ValidatorSetJSONABI = `[ [ { "internalType": "address", - "name": "token", + "name": "validatorAddress", "type": "address" } ], - "name": "setGovernorToken", + "name": "slash", "outputs": [], "stateMutability": "nonpayable", @@ -349,29 +1522,29 @@ const ValidatorSetJSONABI = `[ }, { "inputs": + [], + "name": "stakedAmount", + "outputs": [ { "internalType": "uint256", - "name": "number", + "name": "", "type": "uint256" } ], - "name": "setMinimum", - "outputs": - [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [ { - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "address", + "name": "newOwner", + "type": "address" } ], - "name": "setThreshold", + "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", @@ -380,13 +1553,18 @@ const ValidatorSetJSONABI = `[ { "inputs": [ + { + "internalType": "address", + "name": "validatorAddress", + "type": "address" + }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], - "name": "stake", + "name": "undelegate", "outputs": [], "stateMutability": "nonpayable", @@ -395,7 +1573,7 @@ const ValidatorSetJSONABI = `[ { "inputs": [], - "name": "stakedAmount", + "name": "validatorLength", "outputs": [ { @@ -409,14 +1587,60 @@ const ValidatorSetJSONABI = `[ }, { "inputs": - [], - "name": "threshold", + [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "validatorPools", "outputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, { "internalType": "uint256", - "name": "", + "name": "jailedBefore", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stakedAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stakedReward", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "stakedPerShare", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "slashesCount", "type": "uint256" + }, + { + "internalType": "enum ValidatorSet.Status", + "name": "status", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "jailed", + "type": "bool" } ], "stateMutability": "view", @@ -427,23 +1651,20 @@ const ValidatorSetJSONABI = `[ [ { "internalType": "address", - "name": "newOwner", + "name": "", "type": "address" } ], - "name": "transferOwnership", - "outputs": - [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": - [], - "name": "unstake", + "name": "validatorSlashes", "outputs": - [], - "stateMutability": "nonpayable", + [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", "type": "function" }, { @@ -670,12 +1891,12 @@ const BridgeJSONABI = `[ "inputs": [ { - "internalType": "uint256", - "name": "amount", - "type": "uint256" + "internalType": "address", + "name": "account", + "type": "address" } ], - "name": "addBalance", + "name": "addSigner", "outputs": [], "stateMutability": "nonpayable", @@ -683,17 +1904,17 @@ const BridgeJSONABI = `[ }, { "inputs": + [], + "name": "allowance", + "outputs": [ { - "internalType": "address", - "name": "account", - "type": "address" + "internalType": "uint256", + "name": "", + "type": "uint256" } ], - "name": "addSigner", - "outputs": - [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -716,6 +1937,21 @@ const BridgeJSONABI = `[ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": + [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ @@ -791,6 +2027,21 @@ const BridgeJSONABI = `[ "stateMutability": "view", "type": "function" }, + { + "inputs": + [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ @@ -827,6 +2078,21 @@ const BridgeJSONABI = `[ "stateMutability": "view", "type": "function" }, + { + "inputs": + [], + "name": "paused", + "outputs": + [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], @@ -866,6 +2132,15 @@ const BridgeJSONABI = `[ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": + [], + "name": "setPause", + "outputs": + [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ @@ -917,21 +2192,6 @@ const BridgeJSONABI = `[ "stateMutability": "view", "type": "function" }, - { - "inputs": - [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "subBalance", - "outputs": - [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], diff --git a/contracts/upgrader/upgrader.go b/contracts/upgrader/upgrader.go index 2dba71a7a7..1bb29ac8f8 100644 --- a/contracts/upgrader/upgrader.go +++ b/contracts/upgrader/upgrader.go @@ -1,7 +1,9 @@ +//nolint:lll package upgrader import ( "fmt" + "math/big" "github.com/dogechain-lab/dogechain/chain" "github.com/dogechain-lab/dogechain/contracts/systemcontracts" @@ -12,9 +14,11 @@ import ( ) type UpgradeConfig struct { - ContractAddr types.Address - CommitURL string - Code string + ContractAddr types.Address + CommitURL string + Code string + DefaultInitStorage map[types.Hash]types.Hash // the initial storage must be backward compatible + Rebalance map[types.Address]*big.Int // deprecated, only active in test network } type Upgrade struct { @@ -26,21 +30,47 @@ type Upgrade struct { const ( MainNetChainID = 2000 + DevNetChainID = 668 ) const ( mainNet = "Mainnet" + devNet = "Devnet" ) var ( GenesisHash types.Hash //upgrade config + // pre-portland. deprecated in devnet + _prePortlandUpgrade = make(map[string]*Upgrade) + // portland _portlandUpgrade = make(map[string]*Upgrade) + // detroit + _detroitUpgrade = make(map[string]*Upgrade) ) func init() { - //nolint:lll - _portlandUpgrade[mainNet] = &Upgrade{ + // pre-portland upgrade + _testInt, _ := new(big.Int).SetString("55000000000000000000", 0) + _prePortlandUpgradeContent := &Upgrade{ + UpgradeName: "pre-portland", + Configs: []*UpgradeConfig{ + { + Rebalance: map[types.Address]*big.Int{ + types.StringToAddress("0x1b051e5D1548326284493BfA380E02C3C149Da4E"): _testInt, + types.StringToAddress("0xa516CF76d083b4cBe93Ebdfb85FbE72aFfFb7a0c"): big.NewInt(0), + types.StringToAddress("0xC7aD3276180f8dfb628d975473a81Af2836CDf2b"): big.NewInt(0), + types.StringToAddress("0x521299a363f1847863e4a6c68c91df722d149c3b"): big.NewInt(0), + types.StringToAddress("0x3a9185A6b49617cC2d5BE65aF199B73f24834F4f"): big.NewInt(0), + }, + }, + }, + } + // network supports pre-portland hardfork + _prePortlandUpgrade[devNet] = _prePortlandUpgradeContent + + // portland upgrade + _portlandUpgradeCfg := &Upgrade{ UpgradeName: "portland", Configs: []*UpgradeConfig{ { @@ -50,36 +80,100 @@ func init() { }, }, } + // networks support portland upgrade + _portlandUpgrade[mainNet] = _portlandUpgradeCfg + _portlandUpgrade[devNet] = _portlandUpgradeCfg + + // detroit hardfork + //nolint:lll + _detroitUpgradeContent := &Upgrade{ + UpgradeName: "detroit", + Configs: []*UpgradeConfig{ + { + ContractAddr: systemcontracts.AddrValidatorSetContract, + CommitURL: "https://github.com/dogechain-lab/contracts/commit/675c539c5c06b85e3a9ddc060f14e8d12c97a22e", + Code: "0x608060405234801561001057600080fd5b50600436106103f15760003560e01c80638aee812711610215578063c2a45d4d11610125578063e1a2e863116100b8578063f7c618c111610087578063f7c618c1146108d1578063f90ecacc146108e9578063facd743b146108fc578063fd9275db1461090f578063fda259e01461092257600080fd5b8063e1a2e86314610885578063e589b61e14610898578063ede5d558146108ab578063f2fde38b146108be57600080fd5b8063ced5bcc1116100f4578063ced5bcc114610841578063d0e30db014610849578063d431b1ac14610851578063d946d27a1461085957600080fd5b8063c2a45d4d1461080b578063c60453a81461081e578063c96be4cb14610826578063ca1e78191461083957600080fd5b8063aea0e78b116101a8578063b7ab4db511610177578063b7ab4db5146107c0578063bb872b4a146107d5578063be199738146107e8578063bebe9daf146107f0578063c0541aaa146107f857600080fd5b8063aea0e78b14610795578063aed1d4031461079d578063b46e5520146107a5578063b4e2fad6146107b857600080fd5b80639dbf97db116101e45780639dbf97db146106e9578063a2526bd3146106f1578063a310624f14610746578063ae2b3e831461078257600080fd5b80638aee8127146106925780638da5cb5b146106a557806395b5a5ee146106b657806396a602f3146106c957600080fd5b806349df8d33116103105780636eab12bd116102a3578063751bf20211610272578063751bf20214610648578063766718081461065b5780637a34e211146106635780637bc897de146106765780638043c10c1461068957600080fd5b80636eab12bd146105ee5780636f8568471461061a57806372d29f641461062257806373a3dda61461063557600080fd5b806359cdb14b116102df57806359cdb14b146105be5780635b9db75a146105c65780635c975abb146105d95780636cbe6cd8146105e657600080fd5b806349df8d331461054b5780634d99dd16146105535780634f558e7914610566578063500a15641461059957600080fd5b806325646e1f116103885780633c21ec87116103575780633c21ec87146104f45780633cd1ecf5146104fc57806340a141ff146105255780634878f4011461053857600080fd5b806325646e1f146104c957806332cc6f08146104dc578063346c90a8146104e4578063373d6132146104ec57600080fd5b8063179b4408116103c4578063179b4408146104705780631e83409a146104905780631fe97684146104a357806320146774146104b657600080fd5b8063026e402b146103f65780630397d4581461040b578063097475f71461041e5780630ca95cf11461044f575b600080fd5b610409610404366004613ab5565b610935565b005b610409610419366004613a19565b610c3d565b61043161042c366004613a19565b610cb9565b60405161044699989796959493929190613ca7565b60405180910390f35b61046261045d366004613a34565b610d99565b604051908152602001610446565b61046261047e366004613a19565b601c6020526000908152604090205481565b61040961049e366004613a19565b610dc6565b6104096104b1366004613a19565b6110fe565b6104096104c4366004613b8b565b611212565b6104096104d7366004613ba4565b611275565b600b54610462565b600c54610462565b600854610462565b6104096112e0565b61046261050a366004613a19565b6001600160a01b03166000908152601c602052604090205490565b610409610533366004613a19565b611426565b610409610546366004613b8b565b6116cc565b600a54610462565b610409610561366004613ab5565b61172f565b610589610574366004613b8b565b601d6020526000908152604090205460ff1681565b6040519015158152602001610446565b6009546001600160a01b03165b6040516001600160a01b039091168152602001610446565b601254610462565b6104096105d4366004613b8b565b611ab3565b6013546105899060ff1681565b600f54610462565b6104626105fc366004613a19565b6001600160a01b03166000908152601a602052604090206003015490565b601154610462565b610409610630366004613a67565b611b16565b610409610643366004613a19565b611bae565b610409610656366004613b8b565b611d16565b610462611d79565b610409610671366004613b8b565b611d95565b610462610684366004613a34565b611df8565b61046260145481565b6104096106a0366004613a19565b611fc5565b6001546001600160a01b03166105a6565b6104096106c4366004613a19565b612049565b6104626106d7366004613a19565b601e6020526000908152604090205481565b600d54610462565b61072b6106ff366004613a34565b601b60209081526000928352604080842090915290825290208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610446565b610775610754366004613a19565b6001600160a01b03166000908152601a602052604090206007015460ff1690565b6040516104469190613c86565b610409610790366004613b34565b61236f565b6104626125cd565b6104626125dc565b6104096107b3366004613a19565b612664565b600354610462565b6107c861271a565b6040516104469190613c39565b6104096107e3366004613b8b565b612726565b600e54610462565b610462612789565b610462610806366004613a19565b612795565b610409610819366004613b8b565b6127b6565b610462612819565b610409610834366004613a19565b612825565b6107c8612952565b601054610462565b610409612e93565b610409612fed565b610462610867366004613a19565b6001600160a01b03166000908152601a602052604090206004015490565b610409610893366004613b8b565b61302b565b6104096108a6366004613a19565b61308e565b6107c86108b9366004613adf565b613186565b6104096108cc366004613a19565b613271565b6013546105a69061010090046001600160a01b031681565b6105a66108f7366004613b8b565b613309565b61058961090a366004613a19565b613333565b61040961091d366004613b8b565b613340565b610409610930366004613a19565b6133a3565b32331461095d5760405162461bcd60e51b815260040161095490613d6f565b60405180910390fd5b600260005414156109805760405162461bcd60e51b815260040161095490613e0f565b600260005560135460ff16156109a85760405162461bcd60e51b815260040161095490613d45565b6012548110156109ca5760405162461bcd60e51b815260040161095490613dee565b6009546040516323b872dd60e01b8152336004820152306024820152604481018390526001600160a01b03909116906323b872dd90606401602060405180830381600087803b158015610a1c57600080fd5b505af1158015610a30573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a549190613b12565b506001600160a01b0382166000818152601b602090815260408083203384528252808320938352601a90915290206002600782015460ff166003811115610a9d57610a9d613f44565b14610aba5760405162461bcd60e51b815260040161095490613da6565b6000610af48360010154610aee64e8d4a51000610ae88660050154886000015461346f90919063ffffffff16565b90613482565b9061348e565b90508015610b865760135460405163a9059cbb60e01b8152336004820152602481018390526101009091046001600160a01b03169063a9059cbb90604401602060405180830381600087803b158015610b4c57600080fd5b505af1158015610b60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b849190613b12565b505b610b8e611d79565b60028401558254610b9f908561349a565b8084556005830154610bbc9164e8d4a5100091610ae8919061346f565b60018401556003820154610bd0908561349a565b6003830155600854610be2908561349a565b600855610bf08533866134a6565b6040518481526001600160a01b0386169033907fe5541a6b6103d4fa7e021ed54fad39c66f27a76bd13d374cf6240ae6bd0bb72b906020015b60405180910390a350506001600055505050565b6001546001600160a01b03163314610c675760405162461bcd60e51b815260040161095490613d0e565b600980546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f3792de2485bcc1adb436c37819f81339378f5ec98f5b3534f7b71b70b5d31c0390600090a35050565b601a60205260009081526040902080548190610cd490613ed8565b80601f0160208091040260200160405190810160405280929190818152602001828054610d0090613ed8565b8015610d4d5780601f10610d2257610100808354040283529160200191610d4d565b820191906000526020600020905b815481529060010190602001808311610d3057829003601f168201915b5050505060018301546002840154600385015460048601546005870154600688015460079098015496976001600160a01b03909516969395509193909260ff8082169161010090041689565b6001600160a01b038083166000908152601b60209081526040808320938516835292905220545b92915050565b323314610de55760405162461bcd60e51b815260040161095490613d6f565b60026000541415610e085760405162461bcd60e51b815260040161095490613e0f565b600260005560135460ff1615610e305760405162461bcd60e51b815260040161095490613d45565b6001600160a01b0381166000908152601b6020908152604080832033845290915290208054610e945760405162461bcd60e51b815260206004820152601060248201526f6e6f7468696e6720746f20636c61696d60801b6044820152606401610954565b6001600160a01b0382166000908152601a602052604080822081516101208101909252805482908290610ec690613ed8565b80601f0160208091040260200160405190810160405280929190818152602001828054610ef290613ed8565b8015610f3f5780601f10610f1457610100808354040283529160200191610f3f565b820191906000526020600020905b815481529060010190602001808311610f2257829003601f168201915b505050918352505060018201546001600160a01b0316602082015260028201546040820152600380830154606083015260048301546080830152600583015460a0830152600683015460c0830152600783015460e09092019160ff1690811115610fab57610fab613f44565b6003811115610fbc57610fbc613f44565b815260079190910154610100900460ff161515602090910152600183015460a082015184549293506000926110019291610aee9164e8d4a5100091610ae8919061346f565b905080156110b35760a082015183546110249164e8d4a5100091610ae89161346f565b600184015560135460405163a9059cbb60e01b8152336004820152602481018390526101009091046001600160a01b03169063a9059cbb90604401602060405180830381600087803b15801561107957600080fd5b505af115801561108d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110b19190613b12565b505b6040518181526001600160a01b0385169033907ff7a40077ff7a04c7e61f6f26fb13774259ddf1b6bce9ecf26a8276cdd39926839060200160405180910390a3505060016000555050565b6001546001600160a01b031633146111285760405162461bcd60e51b815260040161095490613d0e565b6001600160a01b0381166000908152601a6020526040902060035461114b6125dc565b116111685760405162461bcd60e51b815260040161095490613e46565b6002600782015460ff16600381111561118357611183613f44565b146111a05760405162461bcd60e51b815260040161095490613da6565b6007810180546001919060ff191682805b021790555060078101546001600160a01b038316907f0c5c3450e67dce49a08116ce351f3a67c5b7d3217c607162d91189a7229174ea9060ff1660038111156111fc576111fc613f44565b6040519081526020015b60405180910390a25050565b6001546001600160a01b0316331461123c5760405162461bcd60e51b815260040161095490613d0e565b600b805490829055604051829082907f3d657b82c31a672b7a8765b72f6f5e966cfb980ed039570a39ccdd70bf19c26690600090a35050565b6001546001600160a01b0316331461129f5760405162461bcd60e51b815260040161095490613d0e565b6010805463ffffffff83169182905560405190919082907f7414a33d82c698855ef5ed249e10e2f7481971f83f98cee8d7023f15ae0e881f90600090a35050565b6001546001600160a01b0316331461130a5760405162461bcd60e51b815260040161095490613d0e565b60005b6004548110156114175760006004828154811061132c5761132c613f70565b60009182526020808320909101546001600160a01b0316808352601a9091526040822090925090600782015460ff16600381111561136c5761136c613f44565b1415611402576001810180546001600160a01b0384166001600160a01b031990911617905560078101805460ff191660021790556113a9826134fc565b5060078101546001600160a01b038316907f0c5c3450e67dce49a08116ce351f3a67c5b7d3217c607162d91189a7229174ea9060ff1660038111156113f0576113f0613f44565b60405190815260200160405180910390a25b5050808061140f90613f13565b91505061130d565b506114246004600061387f565b565b6001546001600160a01b031633146114505760405162461bcd60e51b815260040161095490613d0e565b6001600160a01b0381166000908152601a60205260408082208151610120810190925280548290829061148290613ed8565b80601f01602080910402602001604051908101604052809291908181526020018280546114ae90613ed8565b80156114fb5780601f106114d0576101008083540402835291602001916114fb565b820191906000526020600020905b8154815290600101906020018083116114de57829003601f168201915b505050918352505060018201546001600160a01b0316602082015260028201546040820152600380830154606083015260048301546080830152600583015460a0830152600683015460c0830152600783015460e09092019160ff169081111561156757611567613f44565b600381111561157857611578613f44565b815260079190910154610100900460ff161515602090910152905060008160e0015160038111156115ab576115ab613f44565b14156115e55760405162461bcd60e51b81526020600482015260096024820152681b9bdd08199bdd5b9960ba1b6044820152606401610954565b6060810151156116245760405162461bcd60e51b815260206004820152600a6024820152691a185cc81cdd185ad95960b21b6044820152606401610954565b61162d82613509565b506001600160a01b0382166000908152601a6020526040812090611651828261389d565b506001810180546001600160a01b0319169055600060028201819055600382018190556004820181905560058201819055600682018190556007909101805461ffff191690556040516001600160a01b038416917fe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f191a25050565b6001546001600160a01b031633146116f65760405162461bcd60e51b815260040161095490613d0e565b600e805490829055604051829082907fb64c2fee5d0035d3aa1122935a0d2800f151a0853dd89adbabb795a6190f8be090600090a35050565b32331461174e5760405162461bcd60e51b815260040161095490613d6f565b600260005414156117715760405162461bcd60e51b815260040161095490613e0f565b600260005560135460ff16156117995760405162461bcd60e51b815260040161095490613d45565b6001600160a01b0382166000908152601b602090815260408083203384529091529020805482118015906117cd5750600082115b61180e5760405162461bcd60e51b81526020600482015260126024820152716e6f7468696e6720746f20756e7374616b6560701b6044820152606401610954565b611816611d79565b61182d826002015461182760105490565b9061349a565b1061186c5760405162461bcd60e51b815260206004820152600f60248201526e65706f6368206e6f7420616c6c6f7760881b6044820152606401610954565b6001600160a01b038084166000908152601a6020526040902060018101549091163314156118ce5760405162461bcd60e51b815260206004820152600f60248201526e6f776e6572206e6f7420616c6c6f7760881b6044820152606401610954565b60006118fc8360010154610aee64e8d4a51000610ae88660050154886000015461346f90919063ffffffff16565b9050801561198e5760135460405163a9059cbb60e01b8152336004820152602481018390526101009091046001600160a01b03169063a9059cbb90604401602060405180830381600087803b15801561195457600080fd5b505af1158015611968573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061198c9190613b12565b505b825461199a908561348e565b80845560058301546119b79164e8d4a5100091610ae8919061346f565b600184015560038201546119cb908561348e565b60038301556008546119dd908561348e565b6008556119eb853386613516565b60095460405163a9059cbb60e01b8152336004820152602481018690526001600160a01b039091169063a9059cbb90604401602060405180830381600087803b158015611a3757600080fd5b505af1158015611a4b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a6f9190613b12565b5060408051858152602081018390526001600160a01b0387169133917f3aace7340547de7b9156593a7652dc07ee900cea3fd8f82cb6c9d38b408298029101610c29565b6001546001600160a01b03163314611add5760405162461bcd60e51b815260040161095490613d0e565b6012805490829055604051829082907fc18cde4032061961886377a68e664be72416785473fa678fa416ed2a31c1b10f90600090a35050565b6001600160a01b038281166000908152601a6020526040902060018101549091163314611b555760405162461bcd60e51b815260040161095490613dca565b8151611b6790829060208501906138d7565b50826001600160a01b03167f8a0ff04a1f70e76c640c0520aa1550c775b38ce28af05fdaee467ad160d05fb183604051611ba19190613c94565b60405180910390a2505050565b323314611bcd5760405162461bcd60e51b815260040161095490613d6f565b60135460ff1615611bf05760405162461bcd60e51b815260040161095490613d45565b6000611bfa611d79565b6001600160a01b0383166000908152601a602052604090209091506003600782015460ff166003811115611c3057611c30613f44565b14611c4d5760405162461bcd60e51b815260040161095490613da6565b60018101546001600160a01b03163314611c795760405162461bcd60e51b815260040161095490613dca565b80600201548211611cbc5760405162461bcd60e51b815260206004820152600d60248201526c1cdd1a5b1b081a5b881a985a5b609a1b6044820152606401610954565b60078101805460ff19166002908117909155600060068301819055908201556040518281526001600160a01b038416907f198b4f09d57ab5dbbf891a135940a04087b2544bebf3506cc81e1b64063b6d6590602001611ba1565b6001546001600160a01b03163314611d405760405162461bcd60e51b815260040161095490613d0e565b600f805490829055604051829082907fdf736d7e2a17c66d20e9bd8c8b51ee5d59a97733dde732893d13fee45469f99b90600090a35050565b6000611d90600c544361348290919063ffffffff16565b905090565b6001546001600160a01b03163314611dbf5760405162461bcd60e51b815260040161095490613d0e565b6003805490829055604051829082907fd1d3d2f30aaedd8fa08451e0514e0d6cd5b86a0e19d7d769d0c2a24495beeb0890600090a35050565b6001600160a01b038083166000908152601b6020908152604080832093851683529281528282208351606081018552815480825260018301549382019390935260029091015493810193909352909190611e56576000915050610dc0565b6001600160a01b0384166000908152601a602052604080822081516101208101909252805482908290611e8890613ed8565b80601f0160208091040260200160405190810160405280929190818152602001828054611eb490613ed8565b8015611f015780601f10611ed657610100808354040283529160200191611f01565b820191906000526020600020905b815481529060010190602001808311611ee457829003601f168201915b505050918352505060018201546001600160a01b0316602082015260028201546040820152600380830154606083015260048301546080830152600583015460a0830152600683015460c0830152600783015460e09092019160ff1690811115611f6d57611f6d613f44565b6003811115611f7e57611f7e613f44565b815260079190910154610100900460ff16151560209182015283015160a08201518451929350611fbc92610aee9164e8d4a5100091610ae89161346f565b95945050505050565b6001546001600160a01b03163314611fef5760405162461bcd60e51b815260040161095490613d0e565b601380546001600160a01b03838116610100818102610100600160a81b031985161790945560405193909204169182907fab27a2419bd7a3bc605bff66b38aacb84061d9e20edab7f7680ce52e6fcd925690600090a35050565b3233146120685760405162461bcd60e51b815260040161095490613d6f565b6002600054141561208b5760405162461bcd60e51b815260040161095490613e0f565b600260005560135460ff16156120b35760405162461bcd60e51b815260040161095490613d45565b6001600160a01b038181166000908152601a60205260409020600181015490911633146120f25760405162461bcd60e51b815260040161095490613dca565b6001600160a01b0382166000908152601b60209081526040808320338452909152902080546121585760405162461bcd60e51b81526020600482015260126024820152716e6f7468696e6720746f20756e7374616b6560701b6044820152606401610954565b6003546121636125dc565b116121805760405162461bcd60e51b815260040161095490613e46565b8054600182015460058401546000916121a991610aee9064e8d4a5100090610ae890879061346f565b9050801561223b5760135460405163a9059cbb60e01b8152336004820152602481018390526101009091046001600160a01b03169063a9059cbb90604401602060405180830381600087803b15801561220157600080fd5b505af1158015612215573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122399190613b12565b505b6001600160a01b0385166000908152601b6020908152604080832033845290915281208181556001810182905560020155600384015461227b908361348e565b600385015560078401805460ff1916600117905560085461229c908361348e565b6008556122aa853384613516565b60095460405163a9059cbb60e01b8152336004820152602481018490526001600160a01b039091169063a9059cbb90604401602060405180830381600087803b1580156122f657600080fd5b505af115801561230a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061232e9190613b12565b506040516001600160a01b038616907fe1434e25d6611e0db941968fdc97811c982ac1602e951637d206f5fdda9dd8f190600090a250506001600055505050565b32331461238e5760405162461bcd60e51b815260040161095490613d6f565b600260005414156123b15760405162461bcd60e51b815260040161095490613e0f565b600260005560135460ff16156123d95760405162461bcd60e51b815260040161095490613d45565b6011548110156123fb5760405162461bcd60e51b815260040161095490613dee565b6009546040516323b872dd60e01b8152336004820152306024820152604481018390526001600160a01b03909116906323b872dd90606401602060405180830381600087803b15801561244d57600080fd5b505af1158015612461573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124859190613b12565b506001600160a01b0382166000908152601a6020526040812090600782015460ff1660038111156124b8576124b8613f44565b146124f55760405162461bcd60e51b815260206004820152600d60248201526c185b1c9958591e48195e1a5cdd609a1b6044820152606401610954565b6001600160a01b0383166000908152601b6020908152604080832033845282529091208551909161252a9184918801906138d7565b50600180830180546001600160a01b0319163317905560078301805460ff19168280021790555060038201839055612560611d79565b6002820155828155600854612575908461349a565b600855612581846134fc565b5061258d8433856134a6565b6040516001600160a01b038516907fe366c1c0452ed8eec96861e9e54141ebff23c9ec89fe27b996b45f5ec388498790600090a250506001600055505050565b6000611d906001611827611d79565b6000806125e7612789565b90506000805b8281101561265d57600061260260158361359c565b905060026001600160a01b0382166000908152601a602052604090206007015460ff16600381111561263657612636613f44565b141561264a578261264681613f13565b9350505b508061265581613f13565b9150506125ed565b5092915050565b6001546001600160a01b0316331461268e5760405162461bcd60e51b815260040161095490613d0e565b6001600160a01b0381166000908152601a60205260409020601154816003015410156126cc5760405162461bcd60e51b815260040161095490613dee565b6001600782015460ff1660038111156126e7576126e7613f44565b146127045760405162461bcd60e51b815260040161095490613da6565b6007810180546002919060ff19166001836111b1565b6060611d9060156135a8565b6001546001600160a01b031633146127505760405162461bcd60e51b815260040161095490613d0e565b600a805490829055604051829082907f79a5349732f93288abbb68e251c3dfc325bf3ee6fde7786d919155d39733e0f590600090a35050565b6000611d9060156135b5565b6001600160a01b0381166000908152601960205260408120610dc0906135b5565b6001546001600160a01b031633146127e05760405162461bcd60e51b815260040161095490613d0e565b600d805490829055604051829082907fad65adbcfea0d9e94f88fee2e0422a61d519dc522506374972587aefe7194bd490600090a35050565b6000611d9060176135b5565b4133146128745760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c7920636f696e626173652063616e2063616c6c2066756e6374696f6e006044820152606401610954565b336000908152601e602052604090205461288f90600161349a565b336000908152601e60209081526040808320939093556001600160a01b0384168252601a9052206002600782015460ff1660038111156128d1576128d1613f44565b141561294e5760006128e1611d79565b905060006128fd6001846006015461349a90919063ffffffff16565b6006840181905560408051828152602081018590529192506001600160a01b038616917f83b04ecf7330997e742429a641e136d9f3698c3e9ac9cb9ce0cc2d6da36a244d910160405180910390a250505b5050565b600454606090156129bd5760048054806020026020016040519081016040528092919081815260200182805480156129b357602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311612995575b5050505050905090565b60006129c7612789565b905060008167ffffffffffffffff8111156129e4576129e4613f86565b604051908082528060200260200182016040528015612a0d578160200160208202803683370190505b5090506000805b83811015612aaf576000612a2960158361359c565b905060026001600160a01b0382166000908152601a602052604090206007015460ff166003811115612a5d57612a5d613f44565b1415612a9c5780848481518110612a7657612a76613f70565b6001600160a01b039092166020928302919091019091015282612a9881613f13565b9350505b5080612aa781613f13565b915050612a14565b506000612abb600b5490565b905081811115612acc575080612e8a565b60005b81811015612e885760008190506000601a6000878481518110612af457612af4613f70565b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060405180610120016040529081600082018054612b3890613ed8565b80601f0160208091040260200160405190810160405280929190818152602001828054612b6490613ed8565b8015612bb15780601f10612b8657610100808354040283529160200191612bb1565b820191906000526020600020905b815481529060010190602001808311612b9457829003601f168201915b505050918352505060018201546001600160a01b0316602082015260028201546040820152600380830154606083015260048301546080830152600583015460a0830152600683015460c0830152600783015460e09092019160ff1690811115612c1d57612c1d613f44565b6003811115612c2e57612c2e613f44565b815260079190910154610100900460ff16151560209091015290506000612c56846001613e68565b90505b85811015612df9576000601a6000898481518110612c7957612c79613f70565b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060405180610120016040529081600082018054612cbd90613ed8565b80601f0160208091040260200160405190810160405280929190818152602001828054612ce990613ed8565b8015612d365780601f10612d0b57610100808354040283529160200191612d36565b820191906000526020600020905b815481529060010190602001808311612d1957829003601f168201915b505050918352505060018201546001600160a01b0316602082015260028201546040820152600380830154606083015260048301546080830152600583015460a0830152600683015460c0830152600783015460e09092019160ff1690811115612da257612da2613f44565b6003811115612db357612db3613f44565b815260079190910154610100900460ff161515602090910152606081810151908501519192501115612de6578193508092505b5080612df181613f13565b915050612c59565b50858281518110612e0c57612e0c613f70565b6020026020010151868481518110612e2657612e26613f70565b6020026020010151878581518110612e4057612e40613f70565b60200260200101888581518110612e5957612e59613f70565b6001600160a01b0393841660209182029290920101529116905250819050612e8081613f13565b915050612acf565b505b82525092915050565b413314612ee25760405162461bcd60e51b815260206004820152601f60248201527f4f6e6c7920636f696e626173652063616e2063616c6c2066756e6374696f6e006044820152606401610954565b336000908152601a602052604090206002600782015460ff166003811115612f0c57612f0c613f44565b148015612f1d575060008160030154115b8015612f395750436000908152601d602052604090205460ff16155b15612fea57436000908152601d60205260409020805460ff19166001179055600a54601454612f679161349a565b6014556003810154600a54600091612f8891610ae89064e8d4a5100061346f565b6005830154909150612f9a908261349a565b6005830155600a546004830154612fb09161349a565b6004830155600a5460405190815233907f62bf3b2d86d93be4bb7aa06117c0e1719716f019f3265fdef4df36510ae5070a90602001611206565b50565b6001546001600160a01b031633146130175760405162461bcd60e51b815260040161095490613d0e565b6013805460ff19811660ff90911615179055565b6001546001600160a01b031633146130555760405162461bcd60e51b815260040161095490613d0e565b6011805490829055604051829082907f207082661d623a88e041ad2d52c2d4ddc719880c70c3ab44aa81accff9bd86ed90600090a35050565b6001546001600160a01b031633146130b85760405162461bcd60e51b815260040161095490613d0e565b6001600160a01b0381166000908152601a60205260409020600e54816006015410156130f65760405162461bcd60e51b815260040161095490613dee565b6003546131016125dc565b1161311e5760405162461bcd60e51b815260040161095490613e46565b6000613128611d79565b905061313781611827600f5490565b600283015560078201805460ff191660031790556040518181526001600160a01b038416907feb7d7a49847ec491969db21a0e31b234565a9923145a2d1b56a75c9e9582580290602001611ba1565b606060008267ffffffffffffffff8111156131a3576131a3613f86565b6040519080825280602002602001820160405280156131cc578160200160208202803683370190505b50905060006131da86612795565b90506000855b6131ea8688613e68565b8110156132655782811015613253576001600160a01b038816600090815260196020526040902061321b908261359c565b84838151811061322d5761322d613f70565b6001600160a01b03909216602092830291909101909101528161324f81613f13565b9250505b8061325d81613f13565b9150506131e0565b50825250949350505050565b6001546001600160a01b0316331461329b5760405162461bcd60e51b815260040161095490613d0e565b6001600160a01b0381166133005760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610954565b612fea816135bf565b6004818154811061331957600080fd5b6000918252602090912001546001600160a01b0316905081565b6000610dc0601583613611565b6001546001600160a01b0316331461336a5760405162461bcd60e51b815260040161095490613d0e565b600c805490829055604051829082907f62d532a388a6e5e7ad8089a8aff169a6045b666b20a5a11070805ffc8ed16ee190600090a35050565b6001546001600160a01b031633146133cd5760405162461bcd60e51b815260040161095490613d0e565b6001600160a01b0381166000908152601a602052604090206003600782015460ff16600381111561340057613400613f44565b1461341d5760405162461bcd60e51b815260040161095490613da6565b60078101805460ff19166002908117909155600060068301819055908201556001600160a01b0382167f198b4f09d57ab5dbbf891a135940a04087b2544bebf3506cc81e1b64063b6d656111fc611d79565b600061347b8284613ea2565b9392505050565b600061347b8284613e80565b600061347b8284613ec1565b600061347b8284613e68565b6001600160a01b0382166000908152601c60205260409020546134c9908261349a565b6001600160a01b0383166000908152601c60205260409020556134eb82613633565b506134f68383613640565b50505050565b6000610dc060158361365e565b6000610dc0601583613673565b6001600160a01b0382166000908152601c6020526040902054613539908261348e565b6001600160a01b0383166000908152601c602052604090208190556135635761356182613688565b505b6001600160a01b038084166000908152601b6020908152604080832093861683529290522054613597576134f68383613695565b505050565b600061347b83836136b7565b6060600061347b836136e1565b6000610dc0825490565b600180546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6001600160a01b0381166000908152600183016020526040812054151561347b565b6000610dc060178361365e565b6001600160a01b038216600090815260196020526040812061347b90835b600061347b836001600160a01b03841661373d565b600061347b836001600160a01b03841661378c565b6000610dc0601783613673565b6001600160a01b038216600090815260196020526040812061347b9083613673565b60008260000182815481106136ce576136ce613f70565b9060005260206000200154905092915050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561373157602002820191906000526020600020905b81548152602001906001019080831161371d575b50505050509050919050565b600081815260018301602052604081205461378457508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610dc0565b506000610dc0565b600081815260018301602052604081205480156138755760006137b0600183613ec1565b85549091506000906137c490600190613ec1565b90508181146138295760008660000182815481106137e4576137e4613f70565b906000526020600020015490508087600001848154811061380757613807613f70565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061383a5761383a613f5a565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610dc0565b6000915050610dc0565b5080546000825590600052602060002090810190612fea919061395b565b5080546138a990613ed8565b6000825580601f106138b9575050565b601f016020900490600052602060002090810190612fea919061395b565b8280546138e390613ed8565b90600052602060002090601f016020900481019282613905576000855561394b565b82601f1061391e57805160ff191683800117855561394b565b8280016001018555821561394b579182015b8281111561394b578251825591602001919060010190613930565b5061395792915061395b565b5090565b5b80821115613957576000815560010161395c565b80356001600160a01b038116811461398757600080fd5b919050565b600082601f83011261399d57600080fd5b813567ffffffffffffffff808211156139b8576139b8613f86565b604051601f8301601f19908116603f011681019082821181831017156139e0576139e0613f86565b816040528381528660208588010111156139f957600080fd5b836020870160208301376000602085830101528094505050505092915050565b600060208284031215613a2b57600080fd5b61347b82613970565b60008060408385031215613a4757600080fd5b613a5083613970565b9150613a5e60208401613970565b90509250929050565b60008060408385031215613a7a57600080fd5b613a8383613970565b9150602083013567ffffffffffffffff811115613a9f57600080fd5b613aab8582860161398c565b9150509250929050565b60008060408385031215613ac857600080fd5b613ad183613970565b946020939093013593505050565b600080600060608486031215613af457600080fd5b613afd84613970565b95602085013595506040909401359392505050565b600060208284031215613b2457600080fd5b8151801515811461347b57600080fd5b600080600060608486031215613b4957600080fd5b833567ffffffffffffffff811115613b6057600080fd5b613b6c8682870161398c565b935050613b7b60208501613970565b9150604084013590509250925092565b600060208284031215613b9d57600080fd5b5035919050565b600060208284031215613bb657600080fd5b813563ffffffff8116811461347b57600080fd5b60048110613be857634e487b7160e01b600052602160045260246000fd5b9052565b6000815180845260005b81811015613c1257602081850181015186830182015201613bf6565b81811115613c24576000602083870101525b50601f01601f19169290920160200192915050565b6020808252825182820181905260009190848201906040850190845b81811015613c7a5783516001600160a01b031683529284019291840191600101613c55565b50909695505050505050565b60208101610dc08284613bca565b60208152600061347b6020830184613bec565b6000610120808352613cbb8184018d613bec565b91505060018060a01b038a1660208301528860408301528760608301528660808301528560a08301528460c0830152613cf760e0830185613bca565b8215156101008301529a9950505050505050505050565b6020808252601c908201527f4f6e6c79206f776e65722063616e2063616c6c2066756e6374696f6e00000000604082015260600190565b60208082526010908201526f14185d5cd8589b194e881c185d5cd95960821b604082015260600190565b6020808252601a908201527f4f6e6c7920454f412063616e2063616c6c2066756e6374696f6e000000000000604082015260600190565b6020808252600a90820152696261642073746174757360b01b604082015260600190565b6020808252600a908201526937b7363c9037bbb732b960b11b604082015260600190565b602080825260079082015266746f6f206c6f7760c81b604082015260600190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b602080825260089082015267746f6f206c65737360c01b604082015260600190565b60008219821115613e7b57613e7b613f2e565b500190565b600082613e9d57634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615613ebc57613ebc613f2e565b500290565b600082821015613ed357613ed3613f2e565b500390565b600181811c90821680613eec57607f821691505b60208210811415613f0d57634e487b7160e01b600052602260045260246000fd5b50919050565b6000600019821415613f2757613f27613f2e565b5060010190565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fdfea26469706673582212208a6a273edf434835fe48e693769fb5d82273f3735a620955d164cd3c4ff7b53364736f6c63430008060033", + DefaultInitStorage: map[types.Hash]types.Hash{ + types.StringToHash("0x000000000000000000000000000000000000000a"): types.StringToHash("0xde0b6b3a7640000"), // rewardPerBlock + types.StringToHash("0x000000000000000000000000000000000000000b"): types.StringToHash("0x19"), // activeValidatorsLength + types.StringToHash("0x000000000000000000000000000000000000000c"): types.StringToHash("0x1c20"), // epochBlockInterval + types.StringToHash("0x000000000000000000000000000000000000000d"): types.StringToHash("0x12c"), // misdemeanorThreshold + types.StringToHash("0x000000000000000000000000000000000000000e"): types.StringToHash("0x384"), // felonyThreshold + types.StringToHash("0x000000000000000000000000000000000000000f"): types.StringToHash("0xc"), // validatorJailEpochLength + types.StringToHash("0x0000000000000000000000000000000000000010"): types.StringToHash("0x6"), // minStakePeriod + types.StringToHash("0x0000000000000000000000000000000000000011"): types.StringToHash("0x21e19e0c9bab2400000"), // minValidatorStakeAmount + types.StringToHash("0x0000000000000000000000000000000000000012"): types.StringToHash("0x56bc75e2d63100000"), // minDelegatorStakeAmount + }, + }, + { + ContractAddr: systemcontracts.AddrBridgeContract, + CommitURL: "https://github.com/dogechain-lab/contracts/commit/675c539c5c06b85e3a9ddc060f14e8d12c97a22e", + Code: "0x60806040526004361061012a5760003560e01c806367058d29116100ab5780639dc29fac1161006f5780639dc29fac14610314578063cd86a6cb14610334578063d431b1ac14610354578063de242ff414610369578063eb12d61e1461037f578063f2fde38b1461039f57600080fd5b806367058d291461025c578063715018a61461027c5780637df73e27146102915780638da5cb5b146102ca57806394cf795e146102f257600080fd5b806331fb67c2116100f257806331fb67c2146101ca57806334fcf437146101dd5780634cde3a53146101fd57806354c4633e146102125780635c975abb1461023257600080fd5b806310bad4cf1461012f57806311e330b21461015157806318160ddd1461017157806319e5c034146101955780632c4e722e146101b5575b600080fd5b34801561013b57600080fd5b5061014f61014a366004611084565b6103bf565b005b34801561015d57600080fd5b5061014f61016c366004611084565b610405565b34801561017d57600080fd5b506006545b6040519081526020015b60405180910390f35b3480156101a157600080fd5b5061014f6101b0366004610fc9565b61043c565b3480156101c157600080fd5b50600754610182565b61014f6101d8366004611047565b6106da565b3480156101e957600080fd5b5061014f6101f8366004611084565b6107bf565b34801561020957600080fd5b50600154610182565b34801561021e57600080fd5b5061014f61022d366004610f84565b610824565b34801561023e57600080fd5b5060085461024c9060ff1681565b604051901515815260200161018c565b34801561026857600080fd5b5061014f610277366004611084565b6108b2565b34801561028857600080fd5b5061014f610917565b34801561029d57600080fd5b5061024c6102ac366004610f84565b6001600160a01b031660009081526003602052604090205460ff1690565b3480156102d657600080fd5b506000546040516001600160a01b03909116815260200161018c565b3480156102fe57600080fd5b5061030761094d565b60405161018c9190611192565b34801561032057600080fd5b5061014f61032f366004610f9f565b6109af565b34801561034057600080fd5b5061030761034f366004610fc9565b610a46565b34801561036057600080fd5b5061014f610ae2565b34801561037557600080fd5b5061018260095481565b34801561038b57600080fd5b5061014f61039a366004610f84565b610b20565b3480156103ab57600080fd5b5061014f6103ba366004610f84565b610c0d565b6000546001600160a01b031633146103f25760405162461bcd60e51b81526004016103e990611240565b60405180910390fd5b6009546103ff9082610ca5565b60095550565b6000546001600160a01b0316331461042f5760405162461bcd60e51b81526004016103e990611240565b6009546103ff9082610cb8565b3360009081526003602052604090205460ff1661049b5760405162461bcd60e51b815260206004820152601d60248201527f4f6e6c79207369676e65722063616e2063616c6c2066756e6374696f6e00000060448201526064016103e9565b60085460ff16156104be5760405162461bcd60e51b81526004016103e990611277565b8260095410156105095760405162461bcd60e51b8152602060048201526016602482015275696e73756666696369656e7420616c6c6f77616e636560501b60448201526064016103e9565b600084848484604051602001610522949392919061113d565b60408051808303601f190181529181528151602092830120600081815260059093529120600481015491925090600160a01b900460ff16156105655750506106d4565b60005b81548110156105bc5781543390839083908110610587576105876113bf565b6000918252602090912001546001600160a01b031614156105aa575050506106d4565b806105b481611378565b915050610568565b506004810180546001600160a01b0319166001600160a01b0388161790556001810185905582516105f69060038301906020860190610e42565b50835161060c9060028301906020870190610e42565b5080546001810182556000828152602090200180546001600160a01b031916331790556002805461063d91906112b9565b815411156106d15760048101805460ff60a01b1916600160a01b1790556006546106679086610cb8565b6006556009546106779086610ca5565b600955600181015460048201546040516001600160a01b03909116907fbceab28ca952a9177ce3716580d6c8c2d677fdf721b944e57a5e7322622ffdc9906106c89060028601906003870190611212565b60405180910390a35b50505b50505050565b60085460ff16156106fd5760405162461bcd60e51b81526004016103e990611277565b6001543410156107385760405162461bcd60e51b8152602060048201526006602482015265119bdc989a5960d21b60448201526064016103e9565b600061075b61271061075560075434610cc490919063ffffffff16565b90610cd0565b905060006107693483610ca5565b6006549091506107799082610ca5565b6006556040518290829033907f62116a798bb58cc967874bea4d771de2f9aeec6c64189ff2e5a551072f3106f9906107b29088906111df565b60405180910390a4505050565b6000546001600160a01b031633146107e95760405162461bcd60e51b81526004016103e990611240565b60078054908290556040518290829033907f9e31cca092b9e764bfc6b1b552d55ad4b035e609318fecc26cd38b34e8dd08bb90600090a45050565b6000546001600160a01b0316331461084e5760405162461bcd60e51b81526004016103e990611240565b6001600160a01b03811660009081526003602052604090205460ff16156108af5761087881610cdc565b6040516001600160a01b0382169033907f013d6b862b532c38b01efed34c94d382085143963c63c76e87c24d4b7a37f98e90600090a35b50565b6000546001600160a01b031633146108dc5760405162461bcd60e51b81526004016103e990611240565b60018054908290556040518290829033907f480e8e496f7aff74972b0902e678fd5b564e4fb6527f0418da8a2c1aa628002290600090a45050565b6000546001600160a01b031633146109415760405162461bcd60e51b81526004016103e990611240565b61094b6000610df2565b565b606060028054806020026020016040519081016040528092919081815260200182805480156109a557602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610987575b5050505050905090565b6000546001600160a01b031633146109d95760405162461bcd60e51b81526004016103e990611240565b60085460ff16156109fc5760405162461bcd60e51b81526004016103e990611277565b600654610a099082610ca5565b60065560405181906001600160a01b038416907f696de425f79f4a40bc6d2122ca50507f0efbeabbff86a84871b7196ab8ea8df790600090a35050565b6060600085858585604051602001610a61949392919061113d565b60408051601f1981840301815282825280516020918201206000818152600583528390208054808402860184019094528385529094509190830182828015610ad257602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610ab4575b5050505050915050949350505050565b6000546001600160a01b03163314610b0c5760405162461bcd60e51b81526004016103e990611240565b6008805460ff19811660ff90911615179055565b6000546001600160a01b03163314610b4a5760405162461bcd60e51b81526004016103e990611240565b6001600160a01b03811660009081526003602052604090205460ff166108af57600280546001600160a01b03831660008181526004602090815260408083209490945560039052828120805460ff19166001908117909155845490810185559381527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90930180546001600160a01b031916821790559051909133917f8064a302796c89446a96d63470b5b036212da26bd2debe5bec73e0170a9a5e839190a350565b6000546001600160a01b03163314610c375760405162461bcd60e51b81526004016103e990611240565b6001600160a01b038116610c9c5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016103e9565b6108af81610df2565b6000610cb182846112fa565b9392505050565b6000610cb182846112a1565b6000610cb182846112db565b6000610cb182846112b9565b6001600160a01b038116600090815260046020526040812054600254909190610d07906001906112fa565b9050808214610d8f57600060028281548110610d2557610d256113bf565b600091825260209091200154600280546001600160a01b039092169250829185908110610d5457610d546113bf565b600091825260208083209190910180546001600160a01b0319166001600160a01b039485161790559290911681526004909152604090208290555b6001600160a01b0383166000908152600360209081526040808320805460ff1916905560049091528120556002805480610dcb57610dcb6113a9565b600082815260209020810160001990810180546001600160a01b0319169055019055505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b828054610e4e9061133d565b90600052602060002090601f016020900481019282610e705760008555610eb6565b82601f10610e8957805160ff1916838001178555610eb6565b82800160010185558215610eb6579182015b82811115610eb6578251825591602001919060010190610e9b565b50610ec2929150610ec6565b5090565b5b80821115610ec25760008155600101610ec7565b80356001600160a01b0381168114610ef257600080fd5b919050565b600082601f830112610f0857600080fd5b813567ffffffffffffffff80821115610f2357610f236113d5565b604051601f8301601f19908116603f01168101908282118183101715610f4b57610f4b6113d5565b81604052838152866020858801011115610f6457600080fd5b836020870160208301376000602085830101528094505050505092915050565b600060208284031215610f9657600080fd5b610cb182610edb565b60008060408385031215610fb257600080fd5b610fbb83610edb565b946020939093013593505050565b60008060008060808587031215610fdf57600080fd5b610fe885610edb565b935060208501359250604085013567ffffffffffffffff8082111561100c57600080fd5b61101888838901610ef7565b9350606087013591508082111561102e57600080fd5b5061103b87828801610ef7565b91505092959194509250565b60006020828403121561105957600080fd5b813567ffffffffffffffff81111561107057600080fd5b61107c84828501610ef7565b949350505050565b60006020828403121561109657600080fd5b5035919050565b8054600090600181811c90808316806110b757607f831692505b60208084108214156110d957634e487b7160e01b600052602260045260246000fd5b838852602088018280156110f4576001811461110557611130565b60ff19871682528282019750611130565b60008981526020902060005b8781101561112a57815484820152908601908401611111565b83019850505b5050505050505092915050565b6bffffffffffffffffffffffff198560601b1681528360148201526000835161116d816034850160208801611311565b835190830190611184816034840160208801611311565b016034019695505050505050565b6020808252825182820181905260009190848201906040850190845b818110156111d35783516001600160a01b0316835292840192918401916001016111ae565b50909695505050505050565b60208152600082518060208401526111fe816040850160208701611311565b601f01601f19169190910160400192915050565b604081526000611225604083018561109d565b8281036020840152611237818561109d565b95945050505050565b6020808252601c908201527f4f6e6c79206f776e65722063616e2063616c6c2066756e6374696f6e00000000604082015260600190565b60208082526010908201526f14185d5cd8589b194e881c185d5cd95960821b604082015260600190565b600082198211156112b4576112b4611393565b500190565b6000826112d657634e487b7160e01b600052601260045260246000fd5b500490565b60008160001904831182151516156112f5576112f5611393565b500290565b60008282101561130c5761130c611393565b500390565b60005b8381101561132c578181015183820152602001611314565b838111156106d45750506000910152565b600181811c9082168061135157607f821691505b6020821081141561137257634e487b7160e01b600052602260045260246000fd5b50919050565b600060001982141561138c5761138c611393565b5060010190565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fdfea26469706673582212205b244481ec6aff2fd1940022fe1ab54dff4f36d72c704cbd60ef2cf781da910964736f6c63430008060033", + }, + }, + } + // network supports detroit upgrade + _detroitUpgrade[mainNet] = _detroitUpgradeContent + _detroitUpgrade[devNet] = _detroitUpgradeContent } func UpgradeSystem( chainID int, - config *chain.Forks, + forks *chain.Forks, blockNumber uint64, txn *state.Txn, logger hclog.Logger, ) { - if config == nil || blockNumber == 0 || txn == nil { + if forks == nil || blockNumber == 0 || txn == nil { return } var network string switch chainID { + case DevNetChainID: + network = devNet case MainNetChainID: fallthrough default: network = mainNet } - if config.IsOnPortland(blockNumber) { // only upgrade portland once - up := _portlandUpgrade[network] - applySystemContractUpgrade(up, blockNumber, txn, - logger.With("upgrade", up.UpgradeName, "network", network)) + // only upgrade pre-portland once + if forks.IsOnPreportland(blockNumber) { + up, ok := _prePortlandUpgrade[network] + if ok { + applySystemContractUpgrade(up, blockNumber, txn, forks, + logger.With("upgrade", up.UpgradeName, "network", network)) + } + } + + // only upgrade portland once + if forks.IsOnPortland(blockNumber) { + up, ok := _portlandUpgrade[network] + if ok { // only upgrade network needs to be upgraded + applySystemContractUpgrade(up, blockNumber, txn, forks, + logger.With("upgrade", up.UpgradeName, "network", network)) + } + } + + // only upgrade detroit once + if forks.IsOnDetroit(blockNumber) { + up, ok := _detroitUpgrade[network] + if ok { // only upgrade network needs to be upgraded + applySystemContractUpgrade(up, blockNumber, txn, forks, + logger.With("upgrade", up.UpgradeName, "network", network)) + } } } -func applySystemContractUpgrade(upgrade *Upgrade, blockNumber uint64, txn *state.Txn, logger hclog.Logger) { +func applySystemContractUpgrade( + upgrade *Upgrade, + blockNumber uint64, + txn *state.Txn, + forks *chain.Forks, + logger hclog.Logger, +) { if upgrade == nil { logger.Info("Empty upgrade config", "height", blockNumber) @@ -88,14 +182,28 @@ func applySystemContractUpgrade(upgrade *Upgrade, blockNumber uint64, txn *state logger.Info(fmt.Sprintf("Apply upgrade %s at height %d", upgrade.UpgradeName, blockNumber)) + forksInTime := forks.At(blockNumber) + for _, cfg := range upgrade.Configs { logger.Info(fmt.Sprintf("Upgrade contract %s to commit %s", cfg.ContractAddr.String(), cfg.CommitURL)) + // parse hex code newContractCode, err := hex.DecodeHex(cfg.Code) if err != nil { panic(fmt.Errorf("failed to decode new contract code: %w", err)) } + // set system contract code txn.SetCode(cfg.ContractAddr, newContractCode) + + // initialize system contract storage if necessary + for k, v := range cfg.DefaultInitStorage { + txn.SetStorage(cfg.ContractAddr, k, v, &forksInTime) + } + + // deprecated. Re-set account balance in test cases + for account, balance := range cfg.Rebalance { + txn.SetBalance(account, balance) + } } } diff --git a/contracts/validatorset/query.go b/contracts/validatorset/query.go index 0285ac5ccf..5c31ba1a55 100644 --- a/contracts/validatorset/query.go +++ b/contracts/validatorset/query.go @@ -1,6 +1,7 @@ package validatorset import ( + "bytes" "errors" "math/big" @@ -12,24 +13,38 @@ import ( "github.com/umbracle/go-web3/abi" ) -var ( +const ( + // method + _validatorsMethodName = "validators" + _depositMethodName = "deposit" + _slashMethodName = "slash" +) + +const ( + // parameter name + _depositParameterName = "validatorAddress" + _slashParameterName = "validatorAddress" +) + +const ( // Gas limit used when querying the validator set - queryGasLimit uint64 = 1000000 + SystemTransactionGasLimit uint64 = 1_000_000 +) + +var ( + // some important reuse variable. must exists + _depositMethodID = abis.ValidatorSetABI.Methods[_depositMethodName].ID() + _slashMethodID = abis.ValidatorSetABI.Methods[_slashMethodName].ID() ) func DecodeValidators(method *abi.Method, returnValue []byte) ([]types.Address, error) { - decodedResults, err := method.Outputs.Decode(returnValue) + results, err := abis.DecodeTxMethodOutput(method, returnValue) if err != nil { return nil, err } - results, ok := decodedResults.(map[string]interface{}) - if !ok { - return nil, errors.New("failed type assertion from decodedResults to map") - } - + // type assertion web3Addresses, ok := results["0"].([]web3.Address) - if !ok { return nil, errors.New("failed type assertion from results[0] to []web3.Address") } @@ -42,25 +57,30 @@ func DecodeValidators(method *abi.Method, returnValue []byte) ([]types.Address, return addresses, nil } +type NonceHub interface { + GetNonce(types.Address) uint64 +} + type TxQueryHandler interface { + NonceHub Apply(*types.Transaction) (*runtime.ExecutionResult, error) - GetNonce(types.Address) uint64 } -func QueryValidators(t TxQueryHandler, from types.Address) ([]types.Address, error) { - method, ok := abis.ValidatorSetABI.Methods["validators"] - if !ok { - return nil, errors.New("validators method doesn't exist in Staking contract ABI") +func QueryValidators(t TxQueryHandler, from types.Address, gasLimit uint64) ([]types.Address, error) { + method := abis.ValidatorSetABI.Methods[_validatorsMethodName] + + input, err := abis.EncodeTxMethod(method, nil) + if err != nil { + return nil, err } - selector := method.ID() res, err := t.Apply(&types.Transaction{ From: from, To: &systemcontracts.AddrValidatorSetContract, Value: big.NewInt(0), - Input: selector, + Input: input, GasPrice: big.NewInt(0), - Gas: queryGasLimit, + Gas: gasLimit, Nonce: t.GetNonce(from), }) @@ -74,3 +94,66 @@ func QueryValidators(t TxQueryHandler, from types.Address) ([]types.Address, err return DecodeValidators(method, res.ReturnValue) } + +func MakeDepositTx(t NonceHub, from types.Address) (*types.Transaction, error) { + method := abis.ValidatorSetABI.Methods[_depositMethodName] + + input, err := abis.EncodeTxMethod(method, nil) + if err != nil { + return nil, err + } + + tx := &types.Transaction{ + Nonce: t.GetNonce(from), + GasPrice: big.NewInt(0), + Gas: SystemTransactionGasLimit, + To: &systemcontracts.AddrValidatorSetContract, + Value: nil, + Input: input, + From: from, + } + + return tx, nil +} + +func MakeSlashTx(t NonceHub, from types.Address, needPunished types.Address) (*types.Transaction, error) { + method := abis.ValidatorSetABI.Methods[_slashMethodName] + + input, err := abis.EncodeTxMethod( + method, + map[string]interface{}{ + _slashParameterName: web3.Address(needPunished), + }, + ) + if err != nil { + return nil, err + } + + tx := &types.Transaction{ + Nonce: t.GetNonce(from), + GasPrice: big.NewInt(0), + Gas: SystemTransactionGasLimit, + To: &systemcontracts.AddrValidatorSetContract, + Value: nil, + Input: input, + From: from, + } + + return tx, nil +} + +func IsDepositTransactionSignture(in []byte) bool { + if len(in) < 4 { + return false + } + + return bytes.EqualFold(in[:4], _depositMethodID) +} + +func IsSlashTransactionSignture(in []byte) bool { + if len(in) != 36 { // methodid + address + return false + } + + return bytes.EqualFold(in[:4], _slashMethodID) +} diff --git a/contracts/validatorset/query_test.go b/contracts/validatorset/query_test.go index 3cd0692db3..56d5fb253c 100644 --- a/contracts/validatorset/query_test.go +++ b/contracts/validatorset/query_test.go @@ -49,9 +49,7 @@ func (m *TxMock) Apply(tx *types.Transaction) (*runtime.ExecutionResult, error) return nil, nil } - tx.ComputeHash() - - res, ok := m.hashToRes[tx.Hash] + res, ok := m.hashToRes[tx.Hash()] if ok { return res, nil } @@ -109,7 +107,7 @@ func Test_decodeValidators(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - method := abis.ValidatorSetABI.Methods["validators"] + method := abis.ValidatorSetABI.Methods[_validatorsMethodName] assert.NotNil(t, method) res, err := DecodeValidators(method, tt.value) if tt.succeed { @@ -123,7 +121,7 @@ func Test_decodeValidators(t *testing.T) { } func TestQueryValidators(t *testing.T) { - method := abis.ValidatorSetABI.Methods["validators"] + method := abis.ValidatorSetABI.Methods[_validatorsMethodName] if method == nil { t.Fail() } @@ -185,7 +183,7 @@ func TestQueryValidators(t *testing.T) { Value: big.NewInt(0), Input: method.ID(), GasPrice: big.NewInt(0), - Gas: queryGasLimit, + Gas: SystemTransactionGasLimit, Nonce: 10, }, }, @@ -207,19 +205,19 @@ func TestQueryValidators(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - method := abis.ValidatorSetABI.Methods["validators"] + method := abis.ValidatorSetABI.Methods[_validatorsMethodName] assert.NotNil(t, method) mock := &TxMock{ hashToRes: map[types.Hash]*runtime.ExecutionResult{ - tt.mockArgs.tx.ComputeHash().Hash: tt.mockReturns.res, + tt.mockArgs.tx.Hash(): tt.mockReturns.res, }, nonce: map[types.Address]uint64{ tt.mockArgs.addr: tt.mockReturns.nonce, }, } - res, err := QueryValidators(mock, tt.from) + res, err := QueryValidators(mock, tt.from, tt.mockArgs.tx.Gas) if tt.succeed { assert.NoError(t, err) } else { @@ -229,3 +227,54 @@ func TestQueryValidators(t *testing.T) { }) } } + +func Test_MakeDepositTx_Marshaling(t *testing.T) { + method := abis.ValidatorSetABI.Methods[_depositMethodName] + if method == nil { + t.Errorf("validatorset not supportting method: %s", _depositMethodName) + t.FailNow() + } + + var ( + from = addr1 + mock = &TxMock{ + nonce: map[types.Address]uint64{ + from: 0, + }, + } + ) + + // Marshaling + tx, err := MakeDepositTx(mock, from) + assert.NoError(t, err) + + assert.Equal(t, method.ID(), tx.Input) +} + +func Test_MakeSlashTx_Marshaling(t *testing.T) { + method := abis.ValidatorSetABI.Methods[_slashMethodName] + if method == nil { + t.Errorf("validatorset not supportting method: %s", _slashMethodName) + t.FailNow() + } + + var ( + from = addr1 + mock = &TxMock{ + nonce: map[types.Address]uint64{ + from: 1, + }, + } + punished = addr2 + ) + + // Marshaling + tx, err := MakeSlashTx(mock, from, punished) + assert.NoError(t, err) + + // format expected hash + expectedHash := method.ID() + expectedHash = append(expectedHash, leftPad(addr2.Bytes(), 32)...) // addr2 hash + + assert.Equal(t, expectedHash, tx.Input) +} diff --git a/e2e/framework/helper.go b/e2e/framework/helper.go index 7c0ff28df7..78276c6334 100644 --- a/e2e/framework/helper.go +++ b/e2e/framework/helper.go @@ -14,11 +14,8 @@ import ( "testing" "time" - "github.com/dogechain-lab/dogechain/contracts/abis" "github.com/dogechain-lab/dogechain/contracts/systemcontracts" - "github.com/dogechain-lab/dogechain/contracts/validatorset" "github.com/dogechain-lab/dogechain/crypto" - "github.com/dogechain-lab/dogechain/helper/hex" "github.com/dogechain-lab/dogechain/helper/tests" "github.com/dogechain-lab/dogechain/server/proto" txpoolProto "github.com/dogechain-lab/dogechain/txpool/proto" @@ -58,38 +55,6 @@ func GetAccountBalance(t *testing.T, address types.Address, rpcClient *jsonrpc.C return accountBalance } -// GetValidatorSet returns the validator set from the SC -func GetValidatorSet(from types.Address, rpcClient *jsonrpc.Client) ([]types.Address, error) { - validatorsMethod, ok := abis.ValidatorSetABI.Methods["validators"] - if !ok { - return nil, errors.New("validators method doesn't exist in ValidatorSet contract ABI") - } - - toAddress := web3.Address(systemcontracts.AddrValidatorSetContract) - selector := validatorsMethod.ID() - response, err := rpcClient.Eth().Call( - &web3.CallMsg{ - From: web3.Address(from), - To: &toAddress, - Data: selector, - GasPrice: 100000000, - Value: big.NewInt(0), - }, - web3.Latest, - ) - - if err != nil { - return nil, fmt.Errorf("unable to call ValidatorSet contract method validators, %w", err) - } - - byteResponse, decodeError := hex.DecodeHex(response) - if decodeError != nil { - return nil, fmt.Errorf("unable to decode hex response, %w", decodeError) - } - - return validatorset.DecodeValidators(validatorsMethod, byteResponse) -} - // StakeAmount is a helper function for staking an amount on the ValidatorSet SC func StakeAmount( from types.Address, @@ -147,38 +112,6 @@ func UnstakeAmount( return receipt, nil } -// GetStakedAmount is a helper function for getting the staked amount on the ValidatorSet SC -func GetStakedAmount(from types.Address, rpcClient *jsonrpc.Client) (*big.Int, error) { - stakedAmountMethod, ok := abis.ValidatorSetABI.Methods["stakedAmount"] - if !ok { - return nil, errors.New("stakedAmount method doesn't exist in ValidatorSet contract ABI") - } - - toAddress := web3.Address(systemcontracts.AddrValidatorSetContract) - selector := stakedAmountMethod.ID() - response, err := rpcClient.Eth().Call( - &web3.CallMsg{ - From: web3.Address(from), - To: &toAddress, - Data: selector, - GasPrice: 100000000, - Value: big.NewInt(0), - }, - web3.Latest, - ) - - if err != nil { - return nil, fmt.Errorf("unable to call ValidatorSet contract method stakedAmount, %w", err) - } - - bigResponse, decodeErr := types.ParseUint256orHex(&response) - if decodeErr != nil { - return nil, fmt.Errorf("unable to decode hex response") - } - - return bigResponse, nil -} - func EcrecoverFromBlockhash(hash types.Hash, signature []byte) (types.Address, error) { pubKey, err := crypto.RecoverPubkey(signature, crypto.Keccak256(hash.Bytes())) if err != nil { diff --git a/graphql/graphql.go b/graphql/graphql.go index 0c7edbb902..2a18624cf2 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -312,7 +312,7 @@ func (t *Transaction) findSealedTx(hash types.Hash) bool { // Find the transaction within the block for idx, txn := range block.Transactions { - if txn.Hash == hash { + if txn.Hash() == hash { t.tx = txn t.index = uint64(idx) @@ -937,7 +937,7 @@ func (b *Block) Transactions(ctx context.Context) (*[]*Transaction, error) { ret = append(ret, &Transaction{ backend: b.backend, resolver: b.resolver, - hash: tx.Hash, + hash: tx.Hash(), tx: tx, block: b, index: uint64(i), @@ -962,7 +962,7 @@ func (b *Block) TransactionAt(ctx context.Context, args struct{ Index int32 }) ( return &Transaction{ backend: b.backend, resolver: b.resolver, - hash: tx.Hash, + hash: tx.Hash(), tx: tx, block: b, index: uint64(args.Index), diff --git a/graphql/service.go b/graphql/service.go index 72652b92e6..6f192f3149 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -27,6 +27,7 @@ type Config struct { ChainID uint64 AccessControlAllowOrigin []string BlockRangeLimit uint64 + EnablePProf bool } // GraphQLStore defines all the methods required @@ -73,7 +74,16 @@ func (svc *GraphQLService) setupHTTP() error { return err } - mux := http.DefaultServeMux + var mux *http.ServeMux + if svc.config.EnablePProf { + // debug feature enabled + mux = http.DefaultServeMux + } else { + // NewServeMux must be used, as it disables all debug features. + // For some strange reason, with DefaultServeMux debug/vars is always enabled (but not debug/pprof). + // If pprof need to be enabled, this should be DefaultServeMux + mux = http.NewServeMux() + } // The middleware factory returns a handler, so we need to wrap the handler function properly. graphqlHandler := http.HandlerFunc(svc.handler.ServeHTTP) diff --git a/helper/enode/enode_test.go b/helper/enode/enode_test.go index 4c6cfe7937..258aa04b13 100644 --- a/helper/enode/enode_test.go +++ b/helper/enode/enode_test.go @@ -6,7 +6,7 @@ import ( ) func TestParseEnode(t *testing.T) { - id1 := "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" //nolint:lll + id1 := "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439" enode := func(prefix, id, ip, port string) string { return fmt.Sprintf("%s://%s@%s:%s", prefix, id, ip, port) diff --git a/helper/keccak/keccak.go b/helper/keccak/keccak.go index 9ca74a09fb..c386e59f30 100644 --- a/helper/keccak/keccak.go +++ b/helper/keccak/keccak.go @@ -22,7 +22,6 @@ type Keccak struct { // WriteRlp writes an RLP value func (k *Keccak) WriteRlp(dst []byte, v *fastrlp.Value) []byte { k.buf = v.MarshalTo(k.buf[:0]) - //nolint k.Write(k.buf) return k.Sum(dst) @@ -41,7 +40,6 @@ func (k *Keccak) Reset() { // Read hashes the content and returns the intermediate buffer. func (k *Keccak) Read() []byte { - //nolint k.hash.Read(k.tmp) return k.tmp @@ -49,7 +47,6 @@ func (k *Keccak) Read() []byte { // Sum implements the hash interface func (k *Keccak) Sum(dst []byte) []byte { - //nolint k.hash.Read(k.tmp) dst = append(dst, k.tmp[:]...) diff --git a/helper/keccak/pool.go b/helper/keccak/pool.go index 301ca2ac85..cb728039a4 100644 --- a/helper/keccak/pool.go +++ b/helper/keccak/pool.go @@ -38,7 +38,6 @@ func (p *Pool) Put(k *Keccak) { // Keccak256 hashes a src with keccak-256 func Keccak256(dst, src []byte) []byte { h := DefaultKeccakPool.Get() - //nolint h.Write(src) dst = h.Sum(dst) DefaultKeccakPool.Put(h) diff --git a/helper/kvdb/builder.go b/helper/kvdb/builder.go index 9636192f01..c2545285c2 100644 --- a/helper/kvdb/builder.go +++ b/helper/kvdb/builder.go @@ -20,8 +20,8 @@ const ( DefaultLevelDBCache = 1024 // 1 GiB DefaultLevelDBHandles = 512 // files handles to leveldb open files DefaultLevelDBBloomKeyBits = 2048 // bloom filter bits (256 bytes) - DefaultLevelDBCompactionTableSize = 8 // 8 MiB - DefaultLevelDBCompactionTotalSize = 32 // 32 MiB + DefaultLevelDBCompactionTableSize = 4 // 4 MiB + DefaultLevelDBCompactionTotalSize = 40 // 40 MiB DefaultLevelDBNoSync = false ) @@ -65,12 +65,10 @@ type leveldbBuilder struct { func (builder *leveldbBuilder) SetCacheSize(cacheSize int) LevelDBBuilder { cacheSize = max(cacheSize, minLevelDBCache) - builder.options.BlockCacheCapacity = (cacheSize / 2) * opt.MiB - builder.options.WriteBuffer = (cacheSize / 4) * opt.MiB + builder.options.BlockCacheCapacity = cacheSize * opt.MiB builder.logger.Info("leveldb", - "BlockCacheCapacity", fmt.Sprintf("%d Mib", cacheSize/2), - "WriteBuffer", fmt.Sprintf("%d Mib", cacheSize/4), + "BlockCacheCapacity", fmt.Sprintf("%d Mib", cacheSize), ) return builder @@ -98,9 +96,11 @@ func (builder *leveldbBuilder) SetBloomKeyBits(bloomKeyBits int) LevelDBBuilder func (builder *leveldbBuilder) SetCompactionTableSize(compactionTableSize int) LevelDBBuilder { builder.options.CompactionTableSize = compactionTableSize * opt.MiB + builder.options.WriteBuffer = builder.options.CompactionTableSize * 2 builder.logger.Info("leveldb", "CompactionTableSize", fmt.Sprintf("%d Mib", compactionTableSize), + "WriteBuffer", fmt.Sprintf("%d Mib", builder.options.WriteBuffer/opt.MiB), ) return builder @@ -141,13 +141,17 @@ func NewLevelDBBuilder(logger hclog.Logger, path string) LevelDBBuilder { logger: logger, path: path, options: &opt.Options{ - OpenFilesCacheCapacity: minLevelDBHandles, - CompactionTableSize: DefaultLevelDBCompactionTableSize * opt.MiB, - CompactionTotalSize: DefaultLevelDBCompactionTotalSize * opt.MiB, - BlockCacheCapacity: minLevelDBCache / 2 * opt.MiB, - WriteBuffer: minLevelDBCache / 4 * opt.MiB, - Filter: filter.NewBloomFilter(DefaultLevelDBBloomKeyBits), - NoSync: false, + OpenFilesCacheCapacity: minLevelDBHandles, + CompactionTableSize: DefaultLevelDBCompactionTableSize * opt.MiB, + CompactionTotalSize: DefaultLevelDBCompactionTotalSize * opt.MiB, + BlockCacheCapacity: minLevelDBCache * opt.MiB, + WriteBuffer: (DefaultLevelDBCompactionTableSize * 2) * opt.MiB, + CompactionTableSizeMultiplier: 1.1, // scale size up 1.1 multiple in next level + Filter: filter.NewBloomFilter(DefaultLevelDBBloomKeyBits), + NoSync: false, + BlockSize: 256 * opt.KiB, // default 4kb, but one key-value pair need 0.5kb + FilterBaseLg: 19, // 512kb + DisableSeeksCompaction: true, }, } } diff --git a/jsonrpc/debug_endpoint.go b/jsonrpc/debug_endpoint.go index 0f5fc213e7..520afa13b7 100644 --- a/jsonrpc/debug_endpoint.go +++ b/jsonrpc/debug_endpoint.go @@ -46,7 +46,7 @@ func (d *Debug) TraceTransaction(hash types.Hash) (interface{}, error) { // Find the transaction within the block for idx, txn := range block.Transactions { - if txn.Hash == hash { + if txn.Hash() == hash { tx = txn txIdx = idx diff --git a/jsonrpc/debug_endpoint_test.go b/jsonrpc/debug_endpoint_test.go index 34541e2941..2221ad93bd 100644 --- a/jsonrpc/debug_endpoint_test.go +++ b/jsonrpc/debug_endpoint_test.go @@ -12,7 +12,6 @@ import ( ) func TestDebug_FormatLogs(t *testing.T) { - //nolint:lll var ( stackPc121 = []string{ "0x1ab06ee5", diff --git a/jsonrpc/dispatcher.go b/jsonrpc/dispatcher.go index 7339cf3290..adda923abf 100644 --- a/jsonrpc/dispatcher.go +++ b/jsonrpc/dispatcher.go @@ -104,7 +104,7 @@ func (d *Dispatcher) initEndpoints(store JSONRPCStore) { priceLimit: d.priceLimit, } d.endpoints.Net = &Net{store, d.chainID} - d.endpoints.Web3 = &Web3{} + d.endpoints.Web3 = &Web3{d.chainID} d.endpoints.TxPool = &TxPool{store} d.endpoints.Debug = &Debug{store} } diff --git a/jsonrpc/eth_blockchain_test.go b/jsonrpc/eth_blockchain_test.go index 7696831514..69cc06cb09 100644 --- a/jsonrpc/eth_blockchain_test.go +++ b/jsonrpc/eth_blockchain_test.go @@ -115,7 +115,7 @@ func TestEth_GetTransactionByHash(t *testing.T) { testTxnIndex := 5 testTxn := block.Transactions[testTxnIndex] - res, err := eth.GetTransactionByHash(testTxn.Hash) + res, err := eth.GetTransactionByHash(testTxn.Hash()) assert.NoError(t, err) assert.NotNil(t, res) @@ -138,7 +138,7 @@ func TestEth_GetTransactionByHash(t *testing.T) { testTxn := store.pendingTxns[5] - res, err := eth.GetTransactionByHash(testTxn.Hash) + res, err := eth.GetTransactionByHash(testTxn.Hash()) assert.NoError(t, err) assert.NotNil(t, res) @@ -190,14 +190,14 @@ func TestEth_GetTransactionReceipt(t *testing.T) { rec.SetStatus(types.ReceiptSuccess) store.receipts[hash4] = []*types.Receipt{rec} - res, err := eth.GetTransactionReceipt(txn.Hash) + res, err := eth.GetTransactionReceipt(txn.Hash()) assert.NoError(t, err) assert.NotNil(t, res) //nolint:forcetypeassert response := res.(*receipt) - assert.Equal(t, txn.Hash, response.TxHash) + assert.Equal(t, txn.Hash(), response.TxHash) assert.Equal(t, block.Hash(), response.BlockHash) assert.NotNil(t, response.Logs) }) @@ -444,7 +444,7 @@ func (m *mockBlockStore) Header() *types.Header { func (m *mockBlockStore) ReadTxLookup(txnHash types.Hash) (types.Hash, bool) { for _, block := range m.blocks { for _, txn := range block.Transactions { - if txn.Hash == txnHash { + if txn.Hash() == txnHash { return block.Hash(), true } } @@ -455,7 +455,7 @@ func (m *mockBlockStore) ReadTxLookup(txnHash types.Hash) (types.Hash, bool) { func (m *mockBlockStore) GetPendingTx(txHash types.Hash) (*types.Transaction, bool) { for _, txn := range m.pendingTxns { - if txn.Hash == txHash { + if txn.Hash() == txHash { return txn, true } } diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index 7500d78e52..a614da8129 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -219,13 +219,11 @@ func (e *Eth) SendRawTransaction(input string) (interface{}, error) { return nil, err } - tx.ComputeHash() - if err := e.store.AddTx(tx); err != nil { return nil, err } - return tx.Hash.String(), nil + return tx.Hash().String(), nil } // Reject eth_sendTransaction json-rpc call as we don't support wallet management @@ -257,7 +255,7 @@ func (e *Eth) GetTransactionByHash(hash types.Hash) (interface{}, error) { // Find the transaction within the block for idx, txn := range block.Transactions { - if txn.Hash == hash { + if txn.Hash() == hash { return toTransaction( txn, argUintPtr(block.Number()), @@ -337,23 +335,23 @@ func (e *Eth) GetTransactionReceipt(hash types.Hash) (interface{}, error) { return nil, nil } // find the transaction in the body - indx := -1 + txIndex := -1 for i, txn := range block.Transactions { - if txn.Hash == hash { - indx = i + if txn.Hash() == hash { + txIndex = i break } } - if indx == -1 { + if txIndex == -1 { // txn not found return nil, nil } - txn := block.Transactions[indx] - raw := receipts[indx] + txn := block.Transactions[txIndex] + raw := receipts[txIndex] logs := make([]*Log, len(raw.Logs)) for indx, elem := range raw.Logs { @@ -363,8 +361,8 @@ func (e *Eth) GetTransactionReceipt(hash types.Hash) (interface{}, error) { Data: argBytes(elem.Data), BlockHash: block.Hash(), BlockNumber: argUint64(block.Number()), - TxHash: txn.Hash, - TxIndex: argUint64(indx), + TxHash: txn.Hash(), + TxIndex: argUint64(txIndex), LogIndex: argUint64(indx), Removed: false, } @@ -375,8 +373,8 @@ func (e *Eth) GetTransactionReceipt(hash types.Hash) (interface{}, error) { CumulativeGasUsed: argUint64(raw.CumulativeGasUsed), LogsBloom: raw.LogsBloom, Status: argUint64(*raw.Status), - TxHash: txn.Hash, - TxIndex: argUint64(indx), + TxHash: txn.Hash(), + TxIndex: argUint64(txIndex), BlockHash: block.Hash(), BlockNumber: argUint64(block.Number()), GasUsed: argUint64(raw.GasUsed), @@ -421,8 +419,8 @@ func (e *Eth) GetStorageAt( } // Parse the RLP value p := &fastrlp.Parser{} - v, err := p.Parse(result) + v, err := p.Parse(result) if err != nil { return argBytesPtr(types.ZeroHash[:]), nil } @@ -433,7 +431,8 @@ func (e *Eth) GetStorageAt( return argBytesPtr(types.ZeroHash[:]), nil } - return argBytesPtr(data), nil + // Pad to return 32 bytes data + return argBytesPtr(types.BytesToHash(data).Bytes()), nil } // GasPrice returns the average gas price based on the last x blocks @@ -947,7 +946,7 @@ func (e *Eth) decodeTxn(arg *txnArgs) (*types.Transaction, error) { txn.To = arg.To } - txn.ComputeHash() + txn.Hash() return txn, nil } diff --git a/jsonrpc/eth_endpoint_test.go b/jsonrpc/eth_endpoint_test.go index 954d2901ca..fb7124f374 100644 --- a/jsonrpc/eth_endpoint_test.go +++ b/jsonrpc/eth_endpoint_test.go @@ -145,7 +145,7 @@ func TestEth_DecodeTxn(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.res != nil { - tt.res.ComputeHash() + tt.res.Hash() } store := newMockStore() for addr, acc := range tt.accounts { diff --git a/jsonrpc/eth_txpool_test.go b/jsonrpc/eth_txpool_test.go index a88db8d15a..f73fad7404 100644 --- a/jsonrpc/eth_txpool_test.go +++ b/jsonrpc/eth_txpool_test.go @@ -18,15 +18,15 @@ func TestEth_TxnPool_SendRawTransaction(t *testing.T) { From: addr0, V: big.NewInt(1), } - txn.ComputeHash() + // txn.ComputeHash() data := txn.MarshalRLP() _, err := eth.SendRawTransaction(hex.EncodeToHex(data)) assert.NoError(t, err) - assert.NotEqual(t, store.txn.Hash, types.ZeroHash) + assert.NotEqual(t, store.txn.Hash(), types.ZeroHash) // the hash in the txn pool should match the one we send - if txn.Hash != store.txn.Hash { + if txn.Hash() != store.txn.Hash() { t.Fatal("bad") } } @@ -45,7 +45,7 @@ func TestEth_TxnPool_SendTransaction(t *testing.T) { _, err := eth.SendRawTransaction(hex.EncodeToHex(txToSend.MarshalRLP())) assert.NoError(t, err) - assert.NotEqual(t, store.txn.Hash, types.ZeroHash) + assert.NotEqual(t, store.txn.Hash(), types.ZeroHash) } type mockStoreTxn struct { diff --git a/jsonrpc/filter_manager.go b/jsonrpc/filter_manager.go index 72b01fa4cb..4b52ede307 100644 --- a/jsonrpc/filter_manager.go +++ b/jsonrpc/filter_manager.go @@ -395,7 +395,7 @@ func (f *FilterManager) getLogsFromBlock(query *LogQuery, block *types.Block) ([ Data: argBytes(log.Data), BlockNumber: argUint64(block.Header.Number), BlockHash: block.Header.Hash, - TxHash: block.Transactions[idx].Hash, + TxHash: block.Transactions[idx].Hash(), TxIndex: argUint64(idx), LogIndex: argUint64(logIdx), }) @@ -708,7 +708,7 @@ func (f *FilterManager) appendLogsToFilters(header *types.Header) error { if receipt.TxHash == types.ZeroHash { // Extract tx Hash - receipt.TxHash = block.Transactions[indx].Hash + receipt.TxHash = block.Transactions[indx].Hash() } f.appendLog(&Log{ diff --git a/jsonrpc/jsonrpc.go b/jsonrpc/jsonrpc.go index e8ae283c36..9605fc8401 100644 --- a/jsonrpc/jsonrpc.go +++ b/jsonrpc/jsonrpc.go @@ -1,6 +1,7 @@ package jsonrpc import ( + "encoding/json" "fmt" "io" "net" @@ -8,6 +9,7 @@ import ( "sync" "time" + "github.com/dogechain-lab/dogechain/versioning" "github.com/gorilla/websocket" "github.com/hashicorp/go-hclog" ) @@ -33,6 +35,10 @@ func (s serverType) String() string { } } +const ( + _authoritativeChainName = "Dogechain" +) + // JSONRPC is an API backend type JSONRPC struct { logger hclog.Logger @@ -66,7 +72,9 @@ type Config struct { JSONNamespaces []Namespace EnableWS bool PriceLimit uint64 - Metrics *Metrics + EnablePProf bool // whether pprof enable or not + + Metrics *Metrics } // NewJSONRPC returns the JSONRPC http server @@ -102,7 +110,16 @@ func (j *JSONRPC) setupHTTP() error { return err } - mux := http.DefaultServeMux + var mux *http.ServeMux + if j.config.EnablePProf { + // debug feature enabled + mux = http.DefaultServeMux + } else { + // NewServeMux must be used, as it disables all debug features. + // For some strange reason, with DefaultServeMux debug/vars is always enabled (but not debug/pprof). + // If pprof need to be enabled, this should be DefaultServeMux + mux = http.NewServeMux() + } // The middleware factory returns a handler, so we need to wrap the handler function properly. jsonRPCHandler := http.HandlerFunc(j.handle) @@ -275,32 +292,24 @@ func (j *JSONRPC) handle(w http.ResponseWriter, req *http.Request) { "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization", ) - if (*req).Method == "OPTIONS" { - return - } - - if req.Method == "GET" { - //nolint - w.Write([]byte("Dogechain-Lab Dogechain JSON-RPC")) + switch req.Method { + case http.MethodPost: + j.handleJSONRPCRequest(w, req) + case http.MethodGet: + j.handleGetRequest(w) + case http.MethodOptions: + // nothing to return + default: j.metrics.Errors.Add(1.0) - - return - } - - if req.Method != "POST" { - //nolint w.Write([]byte("method " + req.Method + " not allowed")) - j.metrics.Errors.Add(1.0) - - return } +} +func (j *JSONRPC) handleJSONRPCRequest(w http.ResponseWriter, req *http.Request) { data, err := io.ReadAll(req.Body) - if err != nil { - //nolint - w.Write([]byte(err.Error())) j.metrics.Errors.Add(1.0) + w.Write([]byte(err.Error())) return } @@ -313,17 +322,39 @@ func (j *JSONRPC) handle(w http.ResponseWriter, req *http.Request) { // handle request resp, err := j.dispatcher.Handle(data) - endT := time.Now() - j.metrics.ResponseTime.Observe(endT.Sub(startT).Seconds()) + j.metrics.ResponseTime.Observe(time.Since(startT).Seconds()) if err != nil { - //nolint - w.Write([]byte(err.Error())) j.metrics.Errors.Add(1.0) + w.Write([]byte(err.Error())) } else { - //nolint w.Write(resp) } j.logger.Debug("handle", "response", string(resp)) } + +type GetResponse struct { + Name string `json:"name"` + ChainID uint64 `json:"chain_id"` + Version string `json:"version"` +} + +func (j *JSONRPC) handleGetRequest(writer io.Writer) { + data := &GetResponse{ + Name: _authoritativeChainName, + ChainID: j.config.ChainID, + Version: versioning.Version, + } + + resp, err := json.Marshal(data) + if err != nil { + j.metrics.Errors.Add(1.0) + writer.Write([]byte(err.Error())) + } + + if _, err = writer.Write(resp); err != nil { + j.metrics.Errors.Add(1.0) + writer.Write([]byte(err.Error())) + } +} diff --git a/jsonrpc/jsonrpc_test.go b/jsonrpc/jsonrpc_test.go index 297b4977c2..5b3bd58c24 100644 --- a/jsonrpc/jsonrpc_test.go +++ b/jsonrpc/jsonrpc_test.go @@ -1,10 +1,14 @@ package jsonrpc import ( + "bytes" + "encoding/json" "net" "testing" "github.com/dogechain-lab/dogechain/helper/tests" + "github.com/dogechain-lab/dogechain/versioning" + "github.com/stretchr/testify/assert" "github.com/hashicorp/go-hclog" ) @@ -27,3 +31,37 @@ func TestHTTPServer(t *testing.T) { t.Fatal(err) } } + +func Test_handleGetRequest(t *testing.T) { + var ( + chainName = _authoritativeChainName + chainID = uint64(200) + ) + + jsonRPC := &JSONRPC{ + config: &Config{ + ChainID: chainID, + }, + } + + mockWriter := bytes.NewBuffer(nil) + + jsonRPC.handleGetRequest(mockWriter) + + response := &GetResponse{} + + assert.NoError( + t, + json.Unmarshal(mockWriter.Bytes(), response), + ) + + assert.Equal( + t, + &GetResponse{ + Name: chainName, + ChainID: chainID, + Version: versioning.Version, + }, + response, + ) +} diff --git a/jsonrpc/txpool_endpoint.go b/jsonrpc/txpool_endpoint.go index 69ac8efc21..b15a88000c 100644 --- a/jsonrpc/txpool_endpoint.go +++ b/jsonrpc/txpool_endpoint.go @@ -60,7 +60,7 @@ func toTxPoolTransaction(t *types.Transaction) *txpoolTransaction { To: t.To, Value: argBig(*t.Value), Input: t.Input, - Hash: t.Hash, + Hash: t.Hash(), From: t.From, BlockHash: types.ZeroHash, BlockNumber: nil, diff --git a/jsonrpc/txpool_endpoint_test.go b/jsonrpc/txpool_endpoint_test.go index b2ac2c4a13..e7f60510f2 100644 --- a/jsonrpc/txpool_endpoint_test.go +++ b/jsonrpc/txpool_endpoint_test.go @@ -219,7 +219,6 @@ func newMockTxPoolStore() *mockTxPoolStore { } } -//nolint:lll func (s *mockTxPoolStore) GetTxs(inclQueued bool) (map[types.Address][]*types.Transaction, map[types.Address][]*types.Transaction) { s.includeQueued = inclQueued @@ -244,7 +243,7 @@ func newTestTransaction(nonce uint64, from types.Address) *types.Transaction { S: big.NewInt(1), } - txn.ComputeHash() + txn.Hash() return txn } diff --git a/jsonrpc/types.go b/jsonrpc/types.go index 2a443094bc..a719a11739 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -62,7 +62,7 @@ func toTransaction( V: argBig(*t.V), R: argBig(*t.R), S: argBig(*t.S), - Hash: t.Hash, + Hash: t.Hash(), From: t.From, } @@ -143,7 +143,7 @@ func toBlock(b *types.Block, fullTx bool) *block { } else { res.Transactions = append( res.Transactions, - transactionHash(txn.Hash), + transactionHash(txn.Hash()), ) } } diff --git a/jsonrpc/types_test.go b/jsonrpc/types_test.go index 29c167e747..c374e917b3 100644 --- a/jsonrpc/types_test.go +++ b/jsonrpc/types_test.go @@ -110,10 +110,11 @@ func TestToTransaction_Returns_V_R_S_ValuesWithoutLeading0(t *testing.T) { V: new(big.Int).SetBytes(v), R: new(big.Int).SetBytes(r), S: new(big.Int).SetBytes(s), - Hash: types.Hash{}, From: types.Address{}, } + txn.Hash() + jsonTx := toTransaction(&txn, nil, nil, nil) jsonV, _ := jsonTx.V.MarshalText() diff --git a/jsonrpc/web3_endpoint.go b/jsonrpc/web3_endpoint.go index e462e9f56a..eb33701680 100644 --- a/jsonrpc/web3_endpoint.go +++ b/jsonrpc/web3_endpoint.go @@ -9,11 +9,19 @@ import ( ) // Web3 is the web3 jsonrpc endpoint -type Web3 struct{} +type Web3 struct { + chainID uint64 +} + +var _clientVersionTemplate = "dogechain [chain-id: %d] [version: %s]" // ClientVersion returns the version of the web3 client (web3_clientVersion) func (w *Web3) ClientVersion() (interface{}, error) { - return fmt.Sprintf("dogechain [%s]", versioning.Version), nil + return fmt.Sprintf( + _clientVersionTemplate, + w.chainID, + versioning.Version, + ), nil } // Sha3 returns Keccak-256 (not the standardized SHA3-256) of the given data diff --git a/jsonrpc/web3_endpoint_test.go b/jsonrpc/web3_endpoint_test.go index 7cbd2bf05f..49a02a5201 100644 --- a/jsonrpc/web3_endpoint_test.go +++ b/jsonrpc/web3_endpoint_test.go @@ -27,9 +27,19 @@ func TestWeb3EndpointSha3(t *testing.T) { } func TestWeb3EndpointClientVersion(t *testing.T) { - dispatcher := newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 20, 1000, 0, []Namespace{ - NamespaceWeb3, - }) + chainID := uint64(100) + + dispatcher := newDispatcher( + hclog.NewNullLogger(), + newMockStore(), + chainID, + 20, + 1000, + 0, + []Namespace{ + NamespaceWeb3, + }, + ) resp, err := dispatcher.Handle([]byte(`{ "method": "web3_clientVersion", @@ -40,5 +50,11 @@ func TestWeb3EndpointClientVersion(t *testing.T) { var res string assert.NoError(t, expectJSONResult(resp, &res)) - assert.Contains(t, res, fmt.Sprintf("dogechain [%v]", versioning.Version)) + assert.Contains(t, res, + fmt.Sprintf( + _clientVersionTemplate, + chainID, + versioning.Version, + ), + ) } diff --git a/mainnet-genesis.json b/mainnet-genesis.json index c431d375f7..6c6657b0d7 100644 --- a/mainnet-genesis.json +++ b/mainnet-genesis.json @@ -76,7 +76,8 @@ "EIP150": 0, "EIP158": 0, "EIP155": 0, - "portland": 1981991 + "portland": 1981991, + "detroit": 4116385 }, "chainID": 2000, "engine": { diff --git a/network/bootnodes.go b/network/bootnodes.go index f73176371f..f67a937671 100644 --- a/network/bootnodes.go +++ b/network/bootnodes.go @@ -1,8 +1,9 @@ package network import ( - "github.com/libp2p/go-libp2p-core/peer" "sync/atomic" + + "github.com/libp2p/go-libp2p-core/peer" ) type bootnodesWrapper struct { diff --git a/network/common/common.go b/network/common/common.go index 6c88993747..e2f2e9bd69 100644 --- a/network/common/common.go +++ b/network/common/common.go @@ -3,10 +3,11 @@ package common import ( "errors" "fmt" - "github.com/libp2p/go-libp2p-core/peer" - "github.com/multiformats/go-multiaddr" "regexp" "strings" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/multiformats/go-multiaddr" ) type DialPriority uint64 diff --git a/network/connections.go b/network/connections.go index bce75dc6de..d4a951c397 100644 --- a/network/connections.go +++ b/network/connections.go @@ -1,8 +1,9 @@ package network import ( - "github.com/libp2p/go-libp2p-core/network" "sync/atomic" + + "github.com/libp2p/go-libp2p-core/network" ) // ConnectionInfo keeps track of current connection information diff --git a/secrets/secrets.go b/secrets/secrets.go index 37ca734075..ddd8321545 100644 --- a/secrets/secrets.go +++ b/secrets/secrets.go @@ -2,6 +2,7 @@ package secrets import ( "errors" + "github.com/hashicorp/go-hclog" ) diff --git a/server/config.go b/server/config.go index 24d85c0982..845356491d 100644 --- a/server/config.go +++ b/server/config.go @@ -72,10 +72,12 @@ type JSONRPC struct { BlockRangeLimit uint64 JSONNamespace []string EnableWS bool + EnablePprof bool } type GraphQL struct { GraphQLAddr *net.TCPAddr AccessControlAllowOrigin []string BlockRangeLimit uint64 + EnablePprof bool } diff --git a/server/server.go b/server/server.go index ce0b33ac60..94302d9651 100644 --- a/server/server.go +++ b/server/server.go @@ -96,8 +96,8 @@ var dirPaths = []string{ func newFileLogger(config *Config) (hclog.Logger, error) { logFileWriter, err := os.OpenFile( config.LogFilePath, - os.O_CREATE|os.O_WRONLY|os.O_APPEND, - os.ModeAppend, + os.O_CREATE+os.O_RDWR+os.O_APPEND, + 0640, ) if err != nil { return nil, fmt.Errorf("could not create log file, %w", err) @@ -141,7 +141,8 @@ func newLevelDBBuilder(logger hclog.Logger, config *Config, path string) kvdb.Le path, ) - leveldbBuilder.SetCacheSize(config.LeveldbOptions.CacheSize). + // trie cache + blockchain cache = config.LeveldbOptions.CacheSize / 2 + leveldbBuilder.SetCacheSize(config.LeveldbOptions.CacheSize / 2). SetHandles(config.LeveldbOptions.Handles). SetBloomKeyBits(config.LeveldbOptions.BloomKeyBits). SetCompactionTableSize(config.LeveldbOptions.CompactionTableSize). @@ -220,7 +221,7 @@ func NewServer(config *Config) (*Server, error) { m.stateStorage = stateStorage - st := itrie.NewState(stateStorage) + st := itrie.NewState(stateStorage, m.serverMetrics.trie) m.state = st m.executor = state.NewExecutor(config.Chain.Params, st, logger) @@ -251,6 +252,7 @@ func NewServer(config *Config) (*Server, error) { return nil, err } + // TODO: refactor the design. Executor and blockchain should not rely on each other. m.executor.GetHash = m.blockchain.GetHashHelper { @@ -647,7 +649,7 @@ func (j *jsonRPCHub) StateAtTransaction(block *types.Block, txIndex int) (*state } if _, err := txn.Apply(tx); err != nil { - return nil, fmt.Errorf("transaction %s failed: %w", tx.Hash, err) + return nil, fmt.Errorf("transaction %s failed: %w", tx.Hash(), err) } } @@ -684,6 +686,7 @@ func (s *Server) setupJSONRPC() error { JSONNamespaces: namespaces, EnableWS: s.config.JSONRPC.EnableWS, PriceLimit: s.config.PriceLimit, + EnablePProf: s.config.JSONRPC.EnablePprof, Metrics: s.serverMetrics.jsonrpc, } @@ -719,6 +722,7 @@ func (s *Server) setupGraphQL() error { ChainID: uint64(s.config.Chain.Params.ChainID), AccessControlAllowOrigin: s.config.GraphQL.AccessControlAllowOrigin, BlockRangeLimit: s.config.GraphQL.BlockRangeLimit, + EnablePProf: s.config.GraphQL.EnablePprof, } srv, err := graphql.NewGraphQLService(s.logger, conf) diff --git a/server/server_metrics.go b/server/server_metrics.go index 23edef0b55..ccbfd16665 100644 --- a/server/server_metrics.go +++ b/server/server_metrics.go @@ -6,6 +6,8 @@ import ( "github.com/dogechain-lab/dogechain/jsonrpc" "github.com/dogechain-lab/dogechain/network" "github.com/dogechain-lab/dogechain/txpool" + + itrie "github.com/dogechain-lab/dogechain/state/immutable-trie" ) // serverMetrics holds the metric instances of all sub systems @@ -15,6 +17,7 @@ type serverMetrics struct { network *network.Metrics txpool *txpool.Metrics jsonrpc *jsonrpc.Metrics + trie *itrie.Metrics } // metricProvider serverMetric instance for the given ChainID and nameSpace @@ -26,6 +29,7 @@ func metricProvider(nameSpace string, chainID string, metricsRequired bool) *ser network: network.GetPrometheusMetrics(nameSpace, "chain_id", chainID), txpool: txpool.GetPrometheusMetrics(nameSpace, "chain_id", chainID), jsonrpc: jsonrpc.GetPrometheusMetrics(nameSpace, "chain_id", chainID), + trie: itrie.GetPrometheusMetrics(nameSpace, "chain_id", chainID), } } @@ -35,5 +39,6 @@ func metricProvider(nameSpace string, chainID string, metricsRequired bool) *ser network: network.NilMetrics(), txpool: txpool.NilMetrics(), jsonrpc: jsonrpc.NilMetrics(), + trie: itrie.NilMetrics(), } } diff --git a/state/executor.go b/state/executor.go index d74200fad7..878dcf2ee4 100644 --- a/state/executor.go +++ b/state/executor.go @@ -76,7 +76,8 @@ func (e *Executor) WriteGenesis(alloc map[types.Address]*chain.GenesisAccount) t } } - _, root := txn.Commit(false) + objs := txn.Commit(false) + _, root := snap.Commit(objs) return types.BytesToHash(root) } @@ -92,6 +93,34 @@ type BlockResult struct { TotalGas uint64 } +// ProcessBlock already does all the handling of the whole process +func (e *Executor) ProcessTransactions( + txn *Transition, + gasLimit uint64, + transactions []*types.Transaction, +) (*Transition, error) { + for _, tx := range transactions { + if e.IsStopped() { + // halt more elegantly + return nil, ErrExecutionStop + } + + if tx.ExceedsBlockGasLimit(gasLimit) { + if err := txn.WriteFailedReceipt(tx); err != nil { + return nil, err + } + + continue + } + + if err := txn.Write(tx); err != nil { + return nil, err + } + } + + return txn, nil +} + // ProcessBlock already does all the handling of the whole process func (e *Executor) ProcessBlock( parentRoot types.Hash, @@ -103,23 +132,21 @@ func (e *Executor) ProcessBlock( return nil, err } - txn.block = block - - for _, t := range block.Transactions { + for _, tx := range block.Transactions { if e.IsStopped() { // halt more elegantly return nil, ErrExecutionStop } - if t.ExceedsBlockGasLimit(block.Header.GasLimit) { - if err := txn.WriteFailedReceipt(t); err != nil { + if tx.ExceedsBlockGasLimit(block.Header.GasLimit) { + if err := txn.WriteFailedReceipt(tx); err != nil { return nil, err } continue } - if err := txn.Write(t); err != nil { + if err := txn.Write(tx); err != nil { return nil, err } } @@ -150,6 +177,8 @@ func (e *Executor) GetForksInTime(blockNumber uint64) chain.ForksInTime { return e.config.Forks.At(blockNumber) } +// TODO: It knows too much knowledge of the blockchain. Should limit its knowledge range, +// beginning from parameters. func (e *Executor) BeginTxn( parentRoot types.Hash, header *types.Header, @@ -198,9 +227,6 @@ type Transition struct { // dummy auxState State - // the current block being processed - block *types.Block - r *Executor config chain.ForksInTime state *Txn @@ -209,8 +235,9 @@ type Transition struct { gasPool uint64 // result - receipts []*types.Receipt - totalGas uint64 + receipts []*types.Receipt + totalGas uint64 + totalGasHook func() uint64 // for testing // evmLogger for debugging, set a dummy logger to 'collect' tracing, // then we wouldn't have to judge any tracing flag @@ -236,7 +263,18 @@ func (t *Transition) GetEVMLogger() runtime.EVMLogger { return t.evmLogger } +// HookTotalGas uses hook to return total gas +// +// Use it for testing +func (t *Transition) HookTotalGas(fn func() uint64) { + t.totalGasHook = fn +} + func (t *Transition) TotalGas() uint64 { + if t.totalGasHook != nil { + return t.totalGasHook() + } + return t.totalGas } @@ -261,7 +299,7 @@ func (t *Transition) WriteFailedReceipt(txn *types.Transaction) error { receipt := &types.Receipt{ CumulativeGasUsed: t.totalGas, - TxHash: txn.Hash, + TxHash: txn.Hash(), Logs: t.state.Logs(), } @@ -307,7 +345,7 @@ func (t *Transition) Write(txn *types.Transaction) error { receipt := &types.Receipt{ CumulativeGasUsed: t.totalGas, - TxHash: txn.Hash, + TxHash: txn.Hash(), GasUsed: result.GasUsed, } @@ -321,7 +359,8 @@ func (t *Transition) Write(txn *types.Transaction) error { receipt.SetStatus(types.ReceiptSuccess) } } else { - ss, aux := t.state.Commit(t.config.EIP155) + objs := t.state.Commit(t.config.EIP155) + ss, aux := t.state.snapshot.Commit(objs) t.state = NewTxn(t.auxState, ss) root = aux receipt.Root = types.BytesToHash(root) @@ -399,7 +438,8 @@ func (t *Transition) handleBridgeLogs(msg *types.Transaction, logs []*types.Log) // Commit commits the final result func (t *Transition) Commit() (Snapshot, types.Hash) { - s2, root := t.state.Commit(t.config.EIP155) + objs := t.state.Commit(t.config.EIP155) + s2, root := t.state.snapshot.Commit(objs) return s2, types.BytesToHash(root) } @@ -414,6 +454,13 @@ func (t *Transition) subGasPool(amount uint64) error { return nil } +// IncreaseSystemTransactionGas updates gas pool so that system contract transactions can be sealed. +func (t *Transition) IncreaseSystemTransactionGas(amount uint64) { + t.addGasPool(amount) + // don't forget to increase current context + t.ctx.GasLimit += int64(amount) +} + func (t *Transition) addGasPool(amount uint64) { t.gasPool += amount } @@ -426,10 +473,6 @@ func (t *Transition) Txn() *Txn { return t.state } -func (t *Transition) GetTxnHash() types.Hash { - return t.block.Hash() -} - // Apply applies a new transaction func (t *Transition) Apply(msg *types.Transaction) (*runtime.ExecutionResult, error) { s := t.state.Snapshot() //nolint:ifshort @@ -568,7 +611,7 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er txn := t.state t.logger.Debug("try to apply transaction", - "hash", msg.Hash, "from", msg.From, "nonce", msg.Nonce, "price", msg.GasPrice.String(), + "hash", msg.Hash(), "from", msg.From, "nonce", msg.Nonce, "price", msg.GasPrice.String(), "remainingGas", t.gasPool, "wantGas", msg.Gas) // 0. the basic amount of gas is required @@ -598,7 +641,7 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er return nil, NewTransitionApplicationError(err, false) } - t.logger.Debug("apply transaction would uses gas", "hash", msg.Hash, "gas", intrinsicGasCost) + t.logger.Debug("apply transaction would uses gas", "hash", msg.Hash(), "gas", intrinsicGasCost) // 5. the purchased gas is enough to cover intrinsic usage gasLeft := msg.Gas - intrinsicGasCost diff --git a/state/immutable-trie/encoding_test.go b/state/immutable-trie/encoding_test.go index 9cfc4ecc3a..fd01fb7edc 100644 --- a/state/immutable-trie/encoding_test.go +++ b/state/immutable-trie/encoding_test.go @@ -112,7 +112,6 @@ func TestEncoding_KeyBytesToHexNibbles(t *testing.T) { func TestEncoding_HexCompact(t *testing.T) { // As per the official spec: - //nolint:lll // https://eth.wiki/en/fundamentals/patricia-tree#specification-compact-encoding-of-hex-sequence-with-optional-terminator // hex char bits | node type partial path length // ---------------------------------------------------------- diff --git a/state/immutable-trie/metrics.go b/state/immutable-trie/metrics.go new file mode 100644 index 0000000000..b9c06097e5 --- /dev/null +++ b/state/immutable-trie/metrics.go @@ -0,0 +1,101 @@ +package itrie + +import ( + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/discard" + + prometheus "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" +) + +// Metrics represents the itrie metrics +type Metrics struct { + CodeLruCacheHit metrics.Counter + CodeLruCacheMiss metrics.Counter + CodeLruCacheRead metrics.Counter + CodeLruCacheWrite metrics.Counter + + AccountStateLruCacheHit metrics.Counter + TrieStateLruCacheHit metrics.Counter + + StateLruCacheMiss metrics.Counter +} + +// GetPrometheusMetrics return the blockchain metrics instance +func GetPrometheusMetrics(namespace string, labelsWithValues ...string) *Metrics { + labels := []string{} + + for i := 0; i < len(labelsWithValues); i += 2 { + labels = append(labels, labelsWithValues[i]) + } + + return &Metrics{ + CodeLruCacheHit: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "itrie", + Name: "state_code_lrucache_hit", + Help: "state code cache hit count", + }, labels).With(labelsWithValues...), + CodeLruCacheMiss: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "itrie", + Name: "state_code_lrucache_miss", + Help: "state code cache miss count", + }, labels).With(labelsWithValues...), + CodeLruCacheRead: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "itrie", + Name: "state_code_lrucache_read", + Help: "state code cache read count", + }, labels).With(labelsWithValues...), + CodeLruCacheWrite: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "itrie", + Name: "state_code_lrucache_write", + Help: "state code cache write count", + }, labels).With(labelsWithValues...), + AccountStateLruCacheHit: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "itrie", + Name: "account_state_snapshot_lrucache_hit", + Help: "account state snapshot cache hit count", + }, labels).With(labelsWithValues...), + TrieStateLruCacheHit: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "itrie", + Name: "trie_state_snapshot_lrucache_hit", + Help: "trie state snapshot cache hit count", + }, labels).With(labelsWithValues...), + StateLruCacheMiss: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: "itrie", + Name: "state_snapshot_lrucache_miss", + Help: "trie state snapshot cache miss count", + }, labels).With(labelsWithValues...), + } +} + +// NilMetrics will return the non operational blockchain metrics +func NilMetrics() *Metrics { + return &Metrics{ + CodeLruCacheHit: discard.NewCounter(), + CodeLruCacheMiss: discard.NewCounter(), + CodeLruCacheRead: discard.NewCounter(), + CodeLruCacheWrite: discard.NewCounter(), + + AccountStateLruCacheHit: discard.NewCounter(), + TrieStateLruCacheHit: discard.NewCounter(), + + StateLruCacheMiss: discard.NewCounter(), + } +} + +// NewDummyMetrics will return the no nil blockchain metrics +// TODO: use generic replace this in golang 1.18 +func NewDummyMetrics(metrics *Metrics) *Metrics { + if metrics != nil { + return metrics + } + + return NilMetrics() +} diff --git a/state/immutable-trie/state.go b/state/immutable-trie/state.go index 1f0adc221d..295f65e34d 100644 --- a/state/immutable-trie/state.go +++ b/state/immutable-trie/state.go @@ -10,17 +10,33 @@ import ( "github.com/dogechain-lab/dogechain/types" ) +const ( + codeLruCacheSize = 8192 + trieStateLruCacheSize = 2048 + accountStateLruCacheSize = 4096 +) + type State struct { storage Storage - cache *lru.Cache + + codeLruCache *lru.Cache + trieStateCache *lru.Cache + accountStateCache *lru.Cache + + metrics *Metrics } -func NewState(storage Storage) *State { - cache, _ := lru.New(128) +func NewState(storage Storage, metrics *Metrics) *State { + codeLruCache, _ := lru.New(codeLruCacheSize) + trieStateCache, _ := lru.New(trieStateLruCacheSize) + accountStateCache, _ := lru.New(accountStateLruCacheSize) s := &State{ - storage: storage, - cache: cache, + storage: storage, + trieStateCache: trieStateCache, + accountStateCache: accountStateCache, + codeLruCache: codeLruCache, + metrics: NewDummyMetrics(metrics), } return s @@ -35,11 +51,41 @@ func (s *State) NewSnapshot() state.Snapshot { } func (s *State) SetCode(hash types.Hash, code []byte) error { - return s.storage.SetCode(hash, code) + err := s.storage.SetCode(hash, code) + + if err != nil { + return err + } + + s.codeLruCache.Add(hash, code) + + s.metrics.CodeLruCacheWrite.Add(1) + + return err } func (s *State) GetCode(hash types.Hash) ([]byte, bool) { - return s.storage.GetCode(hash) + defer s.metrics.CodeLruCacheRead.Add(1) + + // find code in cache + if cacheCode, ok := s.codeLruCache.Get(hash); ok { + if code, ok := cacheCode.([]byte); ok { + s.metrics.CodeLruCacheHit.Add(1) + + return code, true + } + } + + s.metrics.CodeLruCacheMiss.Add(1) + + code, ok := s.storage.GetCode(hash) + if ok { + s.codeLruCache.Add(hash, code) + + s.metrics.CodeLruCacheWrite.Add(1) + } + + return code, ok } func (s *State) NewSnapshotAt(root types.Hash) (state.Snapshot, error) { @@ -48,20 +94,27 @@ func (s *State) NewSnapshotAt(root types.Hash) (state.Snapshot, error) { return s.NewSnapshot(), nil } - tt, ok := s.cache.Get(root) + tt, ok := s.trieStateCache.Get(root) if ok { - t, ok := tt.(*Trie) + trie, ok := tt.(*Trie) if !ok { return nil, errors.New("invalid type assertion") } - t.state = s + s.metrics.TrieStateLruCacheHit.Add(1) + + return trie, nil + } + tt, ok = s.accountStateCache.Get(root) + if ok { trie, ok := tt.(*Trie) if !ok { return nil, errors.New("invalid type assertion") } + s.metrics.AccountStateLruCacheHit.Add(1) + return trie, nil } @@ -75,6 +128,8 @@ func (s *State) NewSnapshotAt(root types.Hash) (state.Snapshot, error) { return nil, fmt.Errorf("state not found at hash %s", root) } + s.metrics.StateLruCacheMiss.Add(1) + t := &Trie{ root: n, state: s, @@ -84,6 +139,10 @@ func (s *State) NewSnapshotAt(root types.Hash) (state.Snapshot, error) { return t, nil } -func (s *State) AddState(root types.Hash, t *Trie) { - s.cache.Add(root, t) +func (s *State) AddAccountState(root types.Hash, t *Trie) { + s.accountStateCache.Add(root, t) +} + +func (s *State) AddTrieState(root types.Hash, t *Trie) { + s.trieStateCache.Add(root, t) } diff --git a/state/immutable-trie/state_test.go b/state/immutable-trie/state_test.go index fe7da12a83..40b219a674 100644 --- a/state/immutable-trie/state_test.go +++ b/state/immutable-trie/state_test.go @@ -12,7 +12,7 @@ func TestState(t *testing.T) { func buildPreState(pre state.PreStates) (state.State, state.Snapshot) { storage := NewMemoryStorage() - st := NewState(storage) + st := NewState(storage, nil) snap := st.NewSnapshot() return st, snap diff --git a/state/immutable-trie/trie.go b/state/immutable-trie/trie.go index c33397afb0..101c5f6821 100644 --- a/state/immutable-trie/trie.go +++ b/state/immutable-trie/trie.go @@ -170,7 +170,7 @@ func (t *Trie) Commit(objs []*state.Object) (state.Snapshot, []byte) { accountStateTrie := localTxn.Commit() // Add this to the cache - t.state.AddState(types.BytesToHash(accountStateRoot), accountStateTrie) + t.state.AddAccountState(types.BytesToHash(accountStateRoot), accountStateTrie) account.Root = types.BytesToHash(accountStateRoot) } @@ -201,7 +201,7 @@ func (t *Trie) Commit(objs []*state.Object) (state.Snapshot, []byte) { panic(err) } - t.state.AddState(types.BytesToHash(root), nTrie) + t.state.AddTrieState(types.BytesToHash(root), nTrie) return nTrie, root } diff --git a/state/runtime/evm/state.go b/state/runtime/evm/state.go index d90ff6f6b5..f89bf519d9 100644 --- a/state/runtime/evm/state.go +++ b/state/runtime/evm/state.go @@ -237,7 +237,6 @@ func (c *state) formatPanicDesc() error { // Run executes the virtual machine func (c *state) Run() (ret []byte, vmerr error) { var ( - logger = c.host.GetEVMLogger() needDebug bool //nolint:stylecheck executedIp uint64 @@ -249,14 +248,18 @@ func (c *state) Run() (ret []byte, vmerr error) { // res []byte // result of the opcode execution function ) - // only real tracer need - switch logger.(type) { - case nil: - needDebug = false - case *runtime.DummyLogger: - needDebug = false - default: - needDebug = true + if c.host != nil { + logger := c.host.GetEVMLogger() + + // only real tracer need + switch logger.(type) { + case nil: + needDebug = false + case *runtime.DummyLogger: + needDebug = false + default: + needDebug = true + } } defer func(needDebug bool, vmerr *error) { diff --git a/state/runtime/precompiled/base_test.go b/state/runtime/precompiled/base_test.go index b70013b96e..52ef7fdf9e 100644 --- a/state/runtime/precompiled/base_test.go +++ b/state/runtime/precompiled/base_test.go @@ -1,4 +1,3 @@ -//nolint:lll package precompiled import ( diff --git a/state/runtime/precompiled/bn256_test.go b/state/runtime/precompiled/bn256_test.go index 660b10acd9..6621e28535 100644 --- a/state/runtime/precompiled/bn256_test.go +++ b/state/runtime/precompiled/bn256_test.go @@ -1,4 +1,3 @@ -//nolint:lll package precompiled import "testing" diff --git a/state/runtime/precompiled/modexp_test.go b/state/runtime/precompiled/modexp_test.go index d7978837b3..0199e9208f 100644 --- a/state/runtime/precompiled/modexp_test.go +++ b/state/runtime/precompiled/modexp_test.go @@ -1,4 +1,3 @@ -//nolint:lll package precompiled import ( diff --git a/state/testing.go b/state/testing.go index 630394d098..8533ba2ffa 100644 --- a/state/testing.go +++ b/state/testing.go @@ -94,13 +94,13 @@ func testDeleteCommonStateRoot(t *testing.T, buildPreState buildPreState) { txn.SetState(addr2, hash1, hash1) txn.SetState(addr2, hash2, hash1) - snap2, _ := txn.Commit(false) + snap2, _ := snap.Commit(txn.Commit(false)) txn2 := newTxn(state, snap2) txn2.SetState(addr1, hash0, hash0) txn2.SetState(addr1, hash1, hash0) - snap3, _ := txn2.Commit(false) + snap3, _ := snap2.Commit(txn2.Commit(false)) txn3 := newTxn(state, snap3) assert.Equal(t, hash1, txn3.GetState(addr1, hash2)) @@ -121,7 +121,7 @@ func testWriteState(t *testing.T, buildPreState buildPreState) { assert.Equal(t, hash1, txn.GetState(addr1, hash1)) assert.Equal(t, hash2, txn.GetState(addr1, hash2)) - snap, _ = txn.Commit(false) + snap, _ = snap.Commit(txn.Commit(false)) txn = newTxn(state, snap) assert.Equal(t, hash1, txn.GetState(addr1, hash1)) @@ -136,7 +136,7 @@ func testWriteEmptyState(t *testing.T, buildPreState buildPreState) { // Without EIP150 the data is added txn.SetState(addr1, hash1, hash0) - snap, _ = txn.Commit(false) + snap, _ = snap.Commit(txn.Commit(false)) txn = newTxn(state, snap) assert.True(t, txn.Exist(addr1)) @@ -146,7 +146,7 @@ func testWriteEmptyState(t *testing.T, buildPreState buildPreState) { // With EIP150 the empty data is removed txn.SetState(addr1, hash1, hash0) - snap, _ = txn.Commit(true) + snap, _ = snap.Commit(txn.Commit(true)) txn = newTxn(state, snap) assert.False(t, txn.Exist(addr1)) @@ -163,7 +163,7 @@ func testUpdateStateWithEmpty(t *testing.T, buildPreState buildPreState) { // TODO, test with false (should not be deleted) // TODO, test with balance on the account and nonce - snap, _ = txn.Commit(true) + snap, _ = snap.Commit(txn.Commit(true)) txn = newTxn(state, snap) assert.False(t, txn.Exist(addr1)) @@ -177,7 +177,7 @@ func testSuicideAccountInPreState(t *testing.T, buildPreState buildPreState) { txn := newTxn(state, snap) txn.Suicide(addr1) - snap, _ = txn.Commit(true) + snap, _ = snap.Commit(txn.Commit(true)) txn = newTxn(state, snap) assert.False(t, txn.Exist(addr1)) @@ -195,7 +195,7 @@ func testSuicideAccount(t *testing.T, buildPreState buildPreState) { // Note, even if has commit suicide it still exists in the current txn assert.True(t, txn.Exist(addr1)) - snap, _ = txn.Commit(true) + snap, _ = snap.Commit(txn.Commit(true)) txn = newTxn(state, snap) assert.False(t, txn.Exist(addr1)) @@ -216,7 +216,7 @@ func testSuicideAccountWithData(t *testing.T, buildPreState buildPreState) { txn.SetState(addr1, hash1, hash1) txn.Suicide(addr1) - snap, _ = txn.Commit(true) + snap, _ = snap.Commit(txn.Commit(true)) txn = newTxn(state, snap) @@ -239,7 +239,7 @@ func testSuicideCoinbase(t *testing.T, buildPreState buildPreState) { txn := newTxn(state, snap) txn.Suicide(addr1) txn.AddSealingReward(addr1, big.NewInt(10)) - snap, _ = txn.Commit(true) + snap, _ = snap.Commit(txn.Commit(true)) txn = newTxn(state, snap) assert.Equal(t, big.NewInt(10), txn.GetBalance(addr1)) @@ -293,7 +293,7 @@ func testChangePrestateAccountBalanceToZero(t *testing.T, buildPreState buildPre txn := newTxn(state, snap) txn.SetBalance(addr1, big.NewInt(0)) - snap, _ = txn.Commit(true) + snap, _ = snap.Commit(txn.Commit(true)) txn = newTxn(state, snap) assert.False(t, txn.Exist(addr1)) @@ -307,7 +307,7 @@ func testChangeAccountBalanceToZero(t *testing.T, buildPreState buildPreState) { txn := newTxn(state, snap) txn.SetBalance(addr1, big.NewInt(10)) txn.SetBalance(addr1, big.NewInt(0)) - snap, _ = txn.Commit(true) + snap, _ = snap.Commit(txn.Commit(true)) txn = newTxn(state, snap) assert.False(t, txn.Exist(addr1)) diff --git a/state/txn.go b/state/txn.go index d3d11bc860..caaf7489e4 100644 --- a/state/txn.go +++ b/state/txn.go @@ -4,7 +4,6 @@ import ( "math/big" iradix "github.com/hashicorp/go-immutable-radix" - lru "github.com/hashicorp/golang-lru" "github.com/dogechain-lab/dogechain/chain" "github.com/dogechain-lab/dogechain/crypto" @@ -29,7 +28,6 @@ type Txn struct { state State snapshots []*iradix.Tree txn *iradix.Txn - codeCache *lru.Cache hash *keccak.Keccak } @@ -40,21 +38,18 @@ func NewTxn(state State, snapshot Snapshot) *Txn { func newTxn(state State, snapshot Snapshot) *Txn { i := iradix.New() - codeCache, _ := lru.New(20) - return &Txn{ snapshot: snapshot, state: state, snapshots: []*iradix.Tree{}, txn: i.Txn(), - codeCache: codeCache, hash: keccak.NewKeccak256(), } } func (txn *Txn) hashit(src []byte) []byte { txn.hash.Reset() - txn.hash.Write(src) //nolint + txn.hash.Write(src) // hashit is used to make queries so we do not need to // make copies of the result return txn.hash.Read() @@ -411,19 +406,9 @@ func (txn *Txn) GetCode(addr types.Address) []byte { if object.DirtyCode { return object.Code } - // TODO; Should we move this to state? - v, ok := txn.codeCache.Get(addr) - - if ok { - //nolint:forcetypeassert - return v.([]byte) - } + // TODO: handle error code, _ := txn.state.GetCode(types.BytesToHash(object.Account.CodeHash)) - if len(code) > 0 { - // code might be empty when closed - txn.codeCache.Add(addr, code) - } return code } @@ -597,7 +582,8 @@ func (txn *Txn) CleanDeleteObjects(deleteEmptyObjects bool) { txn.txn.Delete(refundIndex) } -func (txn *Txn) Commit(deleteEmptyObjects bool) (Snapshot, []byte) { +// func (txn *Txn) Commit(deleteEmptyObjects bool) (Snapshot, []byte) { +func (txn *Txn) Commit(deleteEmptyObjects bool) []*Object { txn.CleanDeleteObjects(deleteEmptyObjects) x := txn.txn.Commit() @@ -644,7 +630,5 @@ func (txn *Txn) Commit(deleteEmptyObjects bool) (Snapshot, []byte) { return false }) - t, hash := txn.snapshot.Commit(objs) - - return t, hash + return objs } diff --git a/tests/state_test.go b/tests/state_test.go index d0a5c7eceb..c06f30ab88 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -47,7 +47,7 @@ func RunSpecificTest(t *testing.T, file string, c stateCase, name, fork string, t.Fatal(err) } - s, _, pastRoot := buildState(c.Pre) + s, snapshot, pastRoot := buildState(c.Pre) forks := config.At(uint64(env.Number)) xxx := state.NewExecutor(&chain.Params{Forks: config, ChainID: 1}, s, hclog.NewNullLogger()) @@ -74,7 +74,9 @@ func RunSpecificTest(t *testing.T, file string, c stateCase, name, fork string, // mining rewards txn.AddSealingReward(env.Coinbase, big.NewInt(0)) - _, root := txn.Commit(forks.EIP158) + objs := txn.Commit(forks.EIP155) + _, root := snapshot.Commit(objs) + if !bytes.Equal(root, p.Root.Bytes()) { t.Fatalf( "root mismatch (%s %s %s %d): expected %s but found %s", diff --git a/tests/testing.go b/tests/testing.go index a4f3116246..76f75ff7a9 100644 --- a/tests/testing.go +++ b/tests/testing.go @@ -224,7 +224,7 @@ func (e *exec) UnmarshalJSON(input []byte) error { func buildState( allocs map[types.Address]*chain.GenesisAccount, ) (state.State, state.Snapshot, types.Hash) { - s := itrie.NewState(itrie.NewMemoryStorage()) + s := itrie.NewState(itrie.NewMemoryStorage(), nil) snap := s.NewSnapshot() txn := state.NewTxn(s, snap) @@ -243,7 +243,8 @@ func buildState( } } - snap, root := txn.Commit(false) + objs := txn.Commit(false) + snap, root := snap.Commit(objs) return s, snap, types.BytesToHash(root) } diff --git a/txpool/event_subscription_test.go b/txpool/event_subscription_test.go index 52510af4b2..5b2ad675f2 100644 --- a/txpool/event_subscription_test.go +++ b/txpool/event_subscription_test.go @@ -42,7 +42,6 @@ func shuffleTxPoolEvents( randomEventType := func(supported bool) proto.EventType { for { - //nolint:gosec randNum, _ := rand.Int(rand.Reader, big.NewInt(int64(len(supportedTypes)))) randType := allEvents[randNum.Int64()] diff --git a/txpool/lookup_map.go b/txpool/lookup_map.go index 1fbc03b447..1b49eda946 100644 --- a/txpool/lookup_map.go +++ b/txpool/lookup_map.go @@ -18,11 +18,13 @@ func (m *lookupMap) add(tx *types.Transaction) bool { m.Lock() defer m.Unlock() - if _, exists := m.all[tx.Hash]; exists { + txHash := tx.Hash() + + if _, exists := m.all[txHash]; exists { return false } - m.all[tx.Hash] = tx + m.all[txHash] = tx return true } @@ -33,7 +35,7 @@ func (m *lookupMap) remove(txs ...*types.Transaction) { defer m.Unlock() for _, tx := range txs { - delete(m.all, tx.Hash) + delete(m.all, tx.Hash()) } } diff --git a/txpool/operator.go b/txpool/operator.go index ceb3b60334..bd87972201 100644 --- a/txpool/operator.go +++ b/txpool/operator.go @@ -51,7 +51,7 @@ func (p *TxPool) AddTxn(ctx context.Context, raw *proto.AddTxnReq) (*proto.AddTx } return &proto.AddTxnResp{ - TxHash: txn.Hash.String(), + TxHash: txn.Hash().String(), }, nil } diff --git a/txpool/txpool.go b/txpool/txpool.go index 0b4a564e34..9bde3da7db 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -399,7 +399,7 @@ func (p *TxPool) RemoveExecuted(tx *types.Transaction) { // pop the top most promoted tx account.promoted.pop() - p.logger.Debug("excutables pop out the max price transaction", "hash", tx.Hash, "from", tx.From) + p.logger.Debug("excutables pop out the max price transaction", "hash", tx.Hash(), "from", tx.From) // update state p.gauge.decrease(slotsRequired(tx)) @@ -409,7 +409,7 @@ func (p *TxPool) RemoveExecuted(tx *types.Transaction) { // update executables if tx := account.promoted.peek(); tx != nil { - p.logger.Debug("excutables push in another transaction", "hash", tx.Hash, "from", tx.From) + p.logger.Debug("excutables push in another transaction", "hash", tx.Hash(), "from", tx.From) p.executables.push(tx) } } @@ -496,7 +496,7 @@ func (p *TxPool) Drop(tx *types.Transaction) { // update metrics p.metrics.EnqueueTxs.Add(float64(-1 * len(dropped))) - p.eventManager.signalEvent(proto.EventType_DROPPED, tx.Hash) + p.eventManager.signalEvent(proto.EventType_DROPPED, tx.Hash()) p.logger.Debug("dropped account txs", "num", droppedCount, "next_nonce", nextNonce, @@ -530,7 +530,7 @@ func (p *TxPool) processEvent(event *blockchain.Event) { } for _, tx := range block.Transactions { - oldTxs[tx.Hash] = tx + oldTxs[tx.Hash()] = tx } } @@ -567,7 +567,7 @@ func (p *TxPool) processEvent(event *blockchain.Event) { // Legacy reorg logic // // Update the addTxns in case of reorgs - delete(oldTxs, tx.Hash) + delete(oldTxs, tx.Hash()) } } @@ -671,9 +671,10 @@ func (p *TxPool) validateTx(tx *types.Transaction) error { // successful, an account is created for this address // (only once) and an enqueueRequest is signaled. func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { + // get the hash already from the very beginning p.logger.Debug("add tx", "origin", origin.String(), - "hash", tx.Hash.String(), + "hash", tx.Hash().String(), ) // validate incoming tx @@ -686,8 +687,6 @@ func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { return ErrTxPoolOverflow } - tx.ComputeHash() - // add to index if ok := p.index.add(tx); !ok { return ErrAlreadyKnown @@ -704,7 +703,7 @@ func (p *TxPool) addTx(origin txOrigin, tx *types.Transaction) error { // send request [BLOCKING] p.enqueueReqCh <- enqueueRequest{tx: tx} - p.eventManager.signalEvent(proto.EventType_ADDED, tx.Hash) + p.eventManager.signalEvent(proto.EventType_ADDED, tx.Hash()) return nil } @@ -736,9 +735,9 @@ func (p *TxPool) handleEnqueueRequest(req enqueueRequest) { p.logger.Debug( "replace enquque transaction", "old", - replacedTx.Hash.String(), + replacedTx.Hash().String(), "new", - tx.Hash.String(), + tx.Hash().String(), ) // remove tx index @@ -746,10 +745,10 @@ func (p *TxPool) handleEnqueueRequest(req enqueueRequest) { // gauge, metrics, event p.gauge.decrease(slotsRequired(replacedTx)) p.metrics.EnqueueTxs.Add(-1) - p.eventManager.signalEvent(proto.EventType_REPLACED, replacedTx.Hash) + p.eventManager.signalEvent(proto.EventType_REPLACED, replacedTx.Hash()) } - p.logger.Debug("enqueue request", "hash", tx.Hash.String()) + p.logger.Debug("enqueue request", "hash", tx.Hash()) // state p.gauge.increase(slotsRequired(tx)) @@ -869,12 +868,12 @@ func (p *TxPool) addGossipTx(obj interface{}) { // add tx if err := p.addTx(gossip, tx); err != nil { if errors.Is(err, ErrAlreadyKnown) { - p.logger.Debug("rejecting known tx (gossip)", "hash", tx.Hash.String()) + p.logger.Debug("rejecting known tx (gossip)", "hash", tx.Hash()) return } - p.logger.Error("failed to add broadcast tx", "err", err, "hash", tx.Hash.String()) + p.logger.Error("failed to add broadcast tx", "err", err, "hash", tx.Hash()) } } @@ -939,7 +938,7 @@ func (p *TxPool) Length() uint64 { // toHash returns the hash(es) of given transaction(s) func toHash(txs ...*types.Transaction) (hashes []types.Hash) { for _, tx := range txs { - hashes = append(hashes, tx.Hash) + hashes = append(hashes, tx.Hash()) } return diff --git a/txpool/txpool_test.go b/txpool/txpool_test.go index f4a2a8fa06..6c0a5d00cd 100644 --- a/txpool/txpool_test.go +++ b/txpool/txpool_test.go @@ -396,7 +396,7 @@ func TestDropKnownGossipTx(t *testing.T) { }() <-pool.enqueueReqCh - _, exists := pool.index.get(tx.Hash) + _, exists := pool.index.get(tx.Hash()) assert.True(t, exists) // send tx as gossip (will be discarded) diff --git a/types/buildroot/buildroot_fast.go b/types/buildroot/buildroot_fast.go index ccb9875cbb..2ef5299c84 100644 --- a/types/buildroot/buildroot_fast.go +++ b/types/buildroot/buildroot_fast.go @@ -144,7 +144,7 @@ func (f *FastHasher) Hash(num int, cb func(i int) []byte) ([]byte, bool) { } func (f *FastHasher) hash(dst, b []byte) []byte { - f.k.Write(b) //nolint + f.k.Write(b) dst = f.k.Sum(dst) f.k.Reset() diff --git a/types/receipt.go b/types/receipt.go index 484291e805..6a7826b1bb 100644 --- a/types/receipt.go +++ b/types/receipt.go @@ -111,7 +111,6 @@ func CreateBloom(receipts []*Receipt) (b Bloom) { func (b *Bloom) setEncode(hasher *keccak.Keccak, h []byte) { hasher.Reset() - //nolint hasher.Write(h[:]) buf := hasher.Read() @@ -153,7 +152,6 @@ func (b *Bloom) IsLogInBloom(log *Log) bool { // isByteArrPresent checks if the byte array is possibly present in the Bloom filter func (b *Bloom) isByteArrPresent(hasher *keccak.Keccak, data []byte) bool { hasher.Reset() - //nolint hasher.Write(data[:]) buf := hasher.Read() diff --git a/types/rlp_encoding_test.go b/types/rlp_encoding_test.go index 5af0c28251..4b6c1d4eaf 100644 --- a/types/rlp_encoding_test.go +++ b/types/rlp_encoding_test.go @@ -57,12 +57,9 @@ func TestRLPMarshall_And_Unmarshall_Transaction(t *testing.T) { t.Fatal(err) } - unmarshalledTxn.ComputeHash() + txn.Hash() - txn.Hash = unmarshalledTxn.Hash - if !reflect.DeepEqual(txn, unmarshalledTxn) { - t.Fatal("[ERROR] Unmarshalled transaction not equal to base transaction") - } + assert.Equal(t, txn, unmarshalledTxn) } func TestRLPStorage_Marshall_And_Unmarshall_Receipt(t *testing.T) { diff --git a/types/rlp_unmarshal.go b/types/rlp_unmarshal.go index de71f3e323..f3a5953f52 100644 --- a/types/rlp_unmarshal.go +++ b/types/rlp_unmarshal.go @@ -308,8 +308,6 @@ func (t *Transaction) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) erro len(elems)) } - p.Hash(t.Hash[:0], v) - // nonce if t.Nonce, err = elems[0].GetUint64(); err != nil { return err @@ -359,5 +357,8 @@ func (t *Transaction) UnmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) erro return err } + // cache hash + t.Hash() + return nil } diff --git a/types/transaction.go b/types/transaction.go index 70c5fbe773..befbc664a3 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -19,11 +19,11 @@ type Transaction struct { V *big.Int R *big.Int S *big.Int - Hash Hash From Address // Cache size atomic.Value + hash atomic.Value // time at which the node received the tx ReceivedTime time.Time @@ -33,18 +33,32 @@ func (t *Transaction) IsContractCreation() bool { return t.To == nil } -// ComputeHash computes the hash of the transaction -func (t *Transaction) ComputeHash() *Transaction { +func (t *Transaction) Hash() Hash { + if hash := t.hash.Load(); hash != nil { + //nolint:forcetypeassert + return hash.(Hash) + } + + hash := t.rlpHash() + t.hash.Store(hash) + + return hash +} + +// rlpHash encodes transaction hash. +func (t *Transaction) rlpHash() (h Hash) { ar := marshalArenaPool.Get() hash := keccak.DefaultKeccakPool.Get() + // return it back + defer func() { + keccak.DefaultKeccakPool.Put(hash) + marshalArenaPool.Put(ar) + }() v := t.MarshalRLPWith(ar) - hash.WriteRlp(t.Hash[:0], v) - - marshalArenaPool.Put(ar) - keccak.DefaultKeccakPool.Put(hash) + hash.WriteRlp(h[:0], v) - return t + return h } // Copy returns a deep copy @@ -52,7 +66,6 @@ func (t *Transaction) Copy() *Transaction { tt := &Transaction{ Nonce: t.Nonce, Gas: t.Gas, - Hash: t.Hash, From: t.From, } diff --git a/versioning/versioning.go b/versioning/versioning.go index 354640ddab..bcc3763320 100644 --- a/versioning/versioning.go +++ b/versioning/versioning.go @@ -1,9 +1,11 @@ package versioning +// Embedded by --ldflags on build time +// Versioning should follow the SemVer guidelines +// https://semver.org/ var ( // Version is the main version at the moment. - // Embedded by --ldflags on build time - // Versioning should follow the SemVer guidelines - // https://semver.org/ - Version = "v0.1.0" + Version string // the main version at the moment + Commit string // the git commit that the binary was built on + BuildTime string // the timestamp of the build )